这款基于 React Native 开发的淘宝风格商品分类应用,是电商类应用的典型基础模块,核心实现了可展开折叠的分类卡片、网格化子分类布局、响应式尺寸适配、轻量化视觉设计等电商场景性,全程基于 RN 原生组件开发,无第三方库依赖,既保证了应用的轻量性和可维护性,也为鸿蒙(HarmonyOS)ArkTS 跨端适配奠定了纯净的代码基础。本文将从 RN 端的核心技术实现入手,拆解多层级数据渲染、可折叠组件、响应式网格布局、组件化设计等关键技术点,同时结合鸿蒙 ArkTS 的声明式 UI 特性,梳理组件等价映射、样式兼容、状态管理迁移、布局逻辑对齐的完整跨端适配路径,为电商类应用的跨端开发提供可复用的解决方案。

一、React Native 端

该应用严格遵循 React 生态组件化、单向数据流、状态驱动 UI的核心设计原则,整体架构分为基础层(常量/模拟数据/尺寸工具)、通用组件层(CategoryItem/CategoryCard)、根容器层(TaobaoCategories),各层职责解耦且数据流向清晰,完全贴合电商类应用的开发规范。同时针对电商分类的业务特性,做了响应式尺寸计算、网格布局适配、可折叠交互优化,让界面在不同尺寸设备上都能保持良好的展示效果。

1. 基础层设计

应用的基础层是整个应用的支撑,包含全局图标库、多层级分类模拟数据、设备尺寸工具三部分,均以纯常量/纯函数形式封装,与业务组件完全解耦,这种设计不仅让代码结构更清晰,更让跨端迁移时可实现零修改或少量修改复用,大幅提升跨端开发效率。

  • 全局图标库ICONS 以键值对形式封装了海量电商、生活类 emoji 图标,替代传统的 SVG/图片图标,减少了应用的资源包体积和网络加载开销,同时通过统一的键名调用保证了图标使用的一致性。虽然本应用中直接在分类数据中写入了 emoji 图标,但该图标库为后续功能扩展(如导航栏、操作按钮)提供了完整的图标支撑,且纯字符形式的图标在跨端时可100% 无差异复用
  • 多层级分类模拟数据CATEGORIES 以数组对象形式定义了电商场景典型的一级分类-二级子分类多层级数据结构,一级分类包含 ID、名称、图标、子分类数组,二级子分类包含 ID、名称、封面图,完美贴合淘宝、京东等主流电商的分类数据结构。后续对接后端 API 时,仅需封装异步请求函数替换静态数据,无需修改任何 UI 渲染逻辑,具备极强的业务扩展性;
  • 响应式尺寸工具:通过 Dimensions.get('window').width 获取设备屏幕宽度,并基于此计算二级子分类的项尺寸 ITEM_SIZE,计算公式为 (width - 64) / 4,其中 64 为左右总内边距(32px 每侧),4 为每行展示的子分类数量,这种动态计算方式让网格布局在手机、平板等不同尺寸设备上都能保持每行4项、均匀分布的展示效果,实现了真正的响应式布局,无需为不同设备编写单独的布局样式。
// 设备屏幕宽度获取,跨端核心复用基础
const { width } = Dimensions.get('window');
// 响应式子分类项尺寸计算,适配所有设备宽度
const ITEM_SIZE = (width - 64) / 4;

2. 组件化

电商类应用的分类模块需要兼顾一级分类的折叠展示、二级分类的网格渲染,该应用将核心 UI 拆分为 CategoryItem(二级子分类项)、CategoryCard(一级分类卡片)、TaobaoCategories(根容器)三个细粒度组件,每个组件专注于自身的核心职责,通过属性传参(Props)实现组件间数据通信,通过局部状态控制自身交互,完全符合“单一职责原则”,同时让组件具备极强的复用性——CategoryItem 可直接复用于其他需要网格展示的模块,CategoryCard 可复用于其他多层级分类场景。

(1)CategoryItem

CategoryItem 是最细粒度的展示组件,接收外部传入的 item 二级子分类数据作为 Props,内部完成子分类封面图、名称的渲染,同时封装了点击事件容器,是高度内聚的独立组件。组件中通过 numberOfLines={1} 限制子分类名称仅显示一行,避免名称过长导致的布局错乱,通过 borderRadius: 30 将封面图设置为圆形,贴合主流电商的视觉设计风格,这些细节处理让界面更整洁、更符合用户的视觉习惯。

// 二级子分类项组件,跨端可直接映射为ArkTS基础组件组合
const CategoryItem: React.FC<{ item: any }> = ({ item }) => {
  return (
    <TouchableOpacity style={styles.categoryItem}>
      <Image source={{ uri: item.image }} style={styles.itemImage} />
      <Text style={styles.itemName} numberOfLines={1}>{item.name}</Text>
    </TouchableOpacity>
  );
};
(2)CategoryCard

CategoryCard 是核心业务组件,接收外部传入的 category 一级分类数据作为 Props,内部维护 expanded 局部布尔状态,实现分类卡片的展开/折叠交互,是“状态驱动 UI”的典型实现。组件的核心逻辑分为两部分:

  • 分类头部:包含分类图标、分类名称、展开/折叠箭头,通过 TouchableOpacity 绑定点击事件,点击时通过 setExpanded(!expanded) 切换局部状态,进而驱动箭头图标(▲/▼)和子分类网格的显隐,局部状态仅作用于当前分类卡片,不同卡片的折叠状态相互独立,不会产生干扰;
  • 子分类网格:通过条件渲染 {expanded && <View style={styles.itemsGrid}>...</View>} 控制显隐,当 expandedtrue 时渲染子分类网格,为 false 时隐藏,网格内部遍历 category.items 子分类数组,逐个渲染 CategoryItem 组件,并通过 Props 将子分类数据传递给它,实现多层级数据的联动渲染。

组件中对分类图标做了个性化视觉处理:通过宽高 40px + 圆角 20px 实现圆形背景,搭配浅灰色底色 #f0f0f0,让 emoji 图标更突出,同时通过 flex: 1 设置分类名称的布局权重,让名称占据头部的剩余空间,箭头图标始终靠右对齐,保证了头部布局的美观性和一致性。

(3)TaobaoCategories

根容器组件作为应用的入口,职责被高度简化——仅负责页面头部渲染、分类列表整体布局、遍历一级分类数据渲染 CategoryCard 组件,不维护任何业务状态,也不处理任何具体的交互逻辑,这种设计让根组件的代码极其简洁,后续的功能扩展(如分类搜索、分类筛选)可直接在根组件中新增,不会影响底层的子组件逻辑。

(4)组件通信

应用内组件间的数据通信完全遵循 React 单向数据流原则,数据从根容器自上而下传递给子组件,子组件仅通过 Props 接收数据,不直接修改父组件状态,如需触发状态变更(如折叠/展开),则通过自身的局部状态实现,保证了数据流向的可追溯性和代码的可维护性:

  • 根容器遍历 CATEGORIES 一级分类数据,将单个分类数据通过 category={category} 传递给 CategoryCard
  • CategoryCard 遍历自身的 category.items 二级子分类数据,将单个子分类数据通过 item={item} 传递给 CategoryItem
  • 所有状态均为组件局部状态(如 CategoryCardexpanded),仅作用于当前组件,组件间无状态耦合,大幅降低了代码的复杂度。

3. 视觉

该应用的视觉设计贴合淘宝等主流电商的轻量化、简约化风格,布局上采用垂直流式布局、弹性布局(Flex)、网格布局相结合的方式,所有效果均通过 RN 原生组件和 StyleSheet 实现,未引入任何第三方 UI 库和样式库,核心实现细节贴合电商场景的布局需求,同时保证了跨端适配的便捷性。

(1)弹性布局(Flex)

应用内所有核心布局均基于 RN 原生 Flex 布局实现,通过 flexDirectionjustifyContentalignItemsflexWrapflex 等核心属性,实现了电商分类所需的各种布局效果,且布局代码简洁、可读性强:

  • 分类头部布局categoryHeader 设置 flexDirection: 'row' + alignItems: 'center',实现分类图标、名称、箭头的横向水平排列且垂直居中,配合 flex: 1 设置分类名称的权重,实现“图标左靠、名称占中、箭头右靠”的经典电商头部布局;
  • 子分类网格布局itemsGrid 设置 flexDirection: 'row' + flexWrap: 'wrap',实现子分类项的横向排列并自动换行,配合动态计算的 ITEM_SIZE 作为子分类项的宽度,实现了每行4项、自动换行、均匀分布的网格布局,这是电商类应用展示多品类子分类的标准布局方式;
  • 根容器布局container 设置 flex: 1 占满整个屏幕,content 设置 padding: 16 实现整体内边距,categoriesList 设置 marginBottom: 20 实现分类列表与底部的间距,保证了页面整体的呼吸感。

鸿蒙 ArkTS 作为基于 TypeScript 的扩展语言,与 React Native 共享TypeScript 语法基础、声明式 UI 设计思想、组件化开发理念,这为跨端迁移提供了天然的便利。本次跨端适配遵循**“业务逻辑 100% 复用、组件结构等价映射、状态管理轻量适配、视觉与交互体验完全对齐”的核心原则,整体迁移过程分为基础层复用、组件等价转换、状态管理迁移、样式兼容适配、布局逻辑对齐**五个阶段,核心代码复用率超 85%,仅需完成少量的语法和组件调整,即可实现应用在鸿蒙端的无差异落地。

1. 基础层

应用的基础层是跨端复用的核心,全局图标库 ICONS、多层级分类模拟数据 CATEGORIES、响应式尺寸计算逻辑均为纯 TypeScript 代码,不依赖任何 RN 原生 API 和组件,因此在鸿蒙 ArkTS 端可直接复制复用,无需任何代码修改,仅需将 RN 端的设备尺寸获取 API 替换为鸿蒙端的原生 API,即可实现设备屏幕宽度的精准获取,为响应式布局提供基础。

RN 端通过 Dimensions.get('window').width 获取屏幕宽度,鸿蒙 ArkTS 端通过 WindowUtil 工具类的 getWindowWidth() 方法实现完全等价的功能,且鸿蒙端提供了更简洁的装饰器式状态绑定,可实时响应设备尺寸的变化(如屏幕旋转),适配性更强:

// React Native 端获取屏幕宽度
const { width } = Dimensions.get('window');
const ITEM_SIZE = (width - 64) / 4;

// 鸿蒙 ArkTS 端获取屏幕宽度(支持屏幕旋转实时更新)
import { WindowUtil } from '@kit.ArkUI';
@Entry
@Component
struct TaobaoCategories {
  // 声明屏幕宽度状态,初始化时获取设备宽度
  @State screenWidth: number = WindowUtil.getWindowWidth();
  // 响应式子分类项尺寸计算,与RN端逻辑完全一致
  get ITEM_SIZE(): number {
    return (this.screenWidth - 64) / 4;
  }
}

2. 组件

React Native 与鸿蒙 ArkTS 均采用声明式 UI 设计,核心组件可实现一一等价映射,且两者的组件化开发理念完全一致,因此 RN 端的 CategoryItemCategoryCardTaobaoCategories 三个组件可直接映射为鸿蒙端的同名自定义组件,组件的职责和结构完全不变,仅需调整组件语法、属性名称、使用方式,即可保证组件结构和布局的一致性。该应用中核心组件的等价转换对照表及实现细节如下,覆盖了电商分类页的所有核心 UI 场景:

React Native 组件 鸿蒙 ArkTS 组件 核心属性/语法转换 核心适配点
SafeAreaView SafeArea 直接包裹根组件,无属性转换 适配刘海屏、底部安全区,鸿蒙端更简洁
View Column/Row/Stack style → style,flexDirection → 直接使用 Column/Row 布局容器,ArkTS 按布局方向拆分组件,更贴合电商布局的实际需求
Text Text numberOfLines → maxLines,style → style 文本展示,样式属性基本兼容,仅字重需替换为枚举值
TouchableOpacity 基础组件 + onClick + stateStyles onPress → onClick,activeOpacity → stateStyles 自定义按压样式 可点击元素,鸿蒙端无原生按压组件,自定义实现等价的按压透明反馈
ScrollView Scroll contentContainerStyle → 直接设置子组件样式 滚动容器,鸿蒙端 Scroll 组件更轻量,滚动流畅度更高
Image Image source={{ uri: xxx }} → src=‘xxx’,resizeMode → objectFit 图片展示,鸿蒙端网络图片加载更高效,支持缓存策略
StyleSheet.create 样式对象 + @Styles/@Extend 直接定义样式对象,部分属性名微调 样式管理,鸿蒙端支持装饰器实现样式复用,减少电商分类的样式冗余
(1)根容器

RN 端的 SafeAreaView 用于适配设备的安全区,避免内容被刘海屏、底部虚拟按键遮挡,鸿蒙 ArkTS 端通过 SafeArea 组件实现完全等价的功能,且使用方式更简洁,直接包裹根组件即可,无需额外的样式配置,完美适配鸿蒙设备的各种屏幕形态:

// React Native 端根容器
<SafeAreaView style={styles.container}>
  <View style={styles.header}>{/* 头部 */}</View>
  <ScrollView contentContainerStyle={styles.content}>{/* 分类列表 */}</ScrollView>
</SafeAreaView>

// 鸿蒙 ArkTS 端根容器
@Entry
@Component
struct TaobaoCategories {
  @State screenWidth: number = WindowUtil.getWindowWidth();
  build() {
    SafeArea() { // 安全区适配,与RN端功能完全一致
      Column() {
        // 头部布局,对应RN端styles.header
        Column() { /* 标题+副标题 */ }
          .width('100%')
          .backgroundColor('#ffffff')
          .padding({ top: 40, bottom: 20, left: 20, right: 20 })
        // 滚动容器,对应RN端ScrollView
        Scroll() {
          Column() { /* 分类列表 */ }
            .width('100%')
            .padding(16)
        }
      }
      .width('100%')
      .height('100%')
      .backgroundColor('#f5f5f5')
    }
  }
}

该 React Native 淘宝商品分类应用展示了如何实现一个功能完整、交互友好的商品分类界面,包括分类展示、展开/收起功能、响应式布局等核心特性。通过本文分析的适配策略,可以顺利将其迁移到 HarmonyOS ArkUI 平台,保持核心功能不变。


真实演示案例代码:




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

// 图标库(使用文本替代SVG)
const ICONS = {
  home: '🏠',
  user: '👤',
  cart: '🛒',
  search: '🔍',
  heart: '❤️',
  star: '⭐',
  gift: '🎁',
  bell: '🔔',
  settings: '⚙️',
  more: '☰',
  close: '✕',
  add: '➕',
  minus: '➖',
  like: '👍',
  share: '📤',
  comment: '💬',
  tag: '🏷️',
  category: '📚',
  electronics: '📱',
  fashion: '👕',
  home: '🏠',
  beauty: '💄',
  food: '🍎',
  sports: '⚽',
  books: '📖',
  toys: '🧸',
  travel: '✈️',
  health: '🏥',
  car: '🚗',
  pet: '🐶',
  baby: '👶',
  jewelry: '💎',
  furniture: '🛋️',
  garden: '🌱',
  tools: '🛠️',
  office: '📝',
  music: '🎵',
  movie: '🎬',
  game: '🎮',
  art: '🎨',
  flower: '🌸',
  tree: '🌳',
  sun: '☀️',
  moon: '🌙',
  cloud: '☁️',
  rain: '🌧️',
  snow: '❄️',
  wind: '💨',
  fire: '🔥',
  water: '💧',
  earth: '🌍',
  star_filled: '⭐',
  heart_filled: '❤️',
  thumbs_up_filled: '👍',
  thumbs_down_filled: '👎',
  comment_filled: '💬',
  share_filled: '📤',
  bookmark_filled: '🔖',
  flag: '🚩',
  location: '📍',
  phone: '📞',
  mail: '✉️',
  lock: '🔒',
  unlock: '🔓',
  eye: '👁️',
  eye_closed: '🙈',
  trash: '🗑️',
  edit: '✏️',
  save: '💾',
  download: '⬇️',
  upload: '⬆️',
  print: '🖨️',
  cut: '✂️',
  copy: '📋',
  paste: '📋',
  undo: '↩️',
  redo: '↪️',
  zoom_in: '🔍+',
  zoom_out: '🔍-',
  refresh: '🔄',
  sync: '🔁',
  stop: '⏹️',
  pause: '⏸️',
  play: '▶️',
  next: '⏭️',
  prev: '⏮️',
  volume_up: '🔊',
  volume_down: '🔉',
  volume_mute: '🔇',
  power: '🔌',
  battery: '🔋',
  wifi: '📶',
  bluetooth: '📡',
  airplane: '✈️',
  car_filled: '🚗',
  bike: '🚲',
  train: '🚂',
  bus: '🚌',
  ship: '🚢',
  rocket: '🚀',
  satellite: '🛰️',
  planet: '🪐',
  alien: '👽',
  robot: '🤖',
  ghost: '👻',
  skull: '💀',
  bone: '🦴',
  hand: '✋',
  finger: '👆',
  fist: '✊',
  peace: '✌️',
  ok: '👌',
  thumb_up: '👍',
  thumb_down: '👎',
  victory: '✌️',
  rock: '🤟',
  call_me: '🤙',
  raised_back_of_hand: '🤚',
  raised_hand_with_fingers_splayed: '🖐️',
  middle_finger: '🖕',
  vulcan_salute: '🖖',
  wave: '👋',
  clapping_hands: '👏',
  open_hands: '👐',
  palms_up_together: '🤲',
  handshake: '🤝',
  pray: '🙏',
  writing_hand: '✍️',
  nail_polish: '💅',
  selfie: '🤳',
  flexed_biceps: '💪',
  muscle: '💪',
  leg: '🦵',
  foot: '🦶',
  ear: '👂',
  nose: '👃',
  brain: '🧠',
  tooth: '🦷',
  bone: '🦴',
  eyes: '👀',
  eye_in_speech_bubble: '👁️‍🗨️',
  speaking_head: '🗣️',
  bust_in_silhouette: '👤',
  busts_in_silhouette: '👥',
  footprints: '👣',
  monkey_face: '🐵',
  monkey: '🐒',
  gorilla: '🦍',
  dog: '🐶',
  dog2: '🐕',
  guide_dog: '🦮',
  poodle: '🐩',
  wolf: '🐺',
  fox: '🦊',
  cat: '🐱',
  lion: '🦁',
  tiger: '🐯',
  cow: '🐮',
  pig: '🐷',
  frog: '🐸',
  turtle: '🐢',
  snake: '🐍',
  eagle: '🦅',
  duck: '🦆',
  owl: '🦉',
  bat: '🦇',
  whale: '🐳',
  fish: '🐟',
  octopus: '🐙',
  shell: '🐚',
  snail: '🐌',
  butterfly: '🦋',
  bug: '🐛',
  ant: '🐜',
  bee: '🐝',
  ladybug: '🐞',
  cricket: '🦗',
  spider: '🕷️',
  scorpion: '🦂',
  mosquito: '🦟',
  microbe: '🦠',
  bouquet: '💐',
  cherry_blossom: '🌸',
  white_flower: '💮',
  rosette: '🏵️',
  rose: '🌹',
  wilted_flower: '🥀',
  hibiscus: '🌺',
  sunflower: '🌻',
  blossom: '🌼',
  tulip: '🌷',
  seedling: '🌱',
  evergreen_tree: '🌲',
  deciduous_tree: '🌳',
  palm_tree: '🌴',
  cactus: '🌵',
  ear_of_rice: '🌾',
  herb: '🌿',
  shamrock: '☘️',
  four_leaf_clover: '🍀',
  maple_leaf: '🍁',
  fallen_leaf: '🍂',
  leaves: '🍃',
  grapes: '🍇',
  melon: '🍈',
  watermelon: '🍉',
  tangerine: '🍊',
  lemon: '🍋',
  banana: '🍌',
  pineapple: '🍍',
  mango: '🥭',
  apple: '🍎',
  green_apple: '🍏',
  pear: '🍐',
  peach: '🍑',
  cherries: '🍒',
  strawberry: '🍓",
  kiwi: '🥝',
  tomato: '🍅',
  coconut: '🥥',
  avocado: '🥑',
  eggplant: '🍆',
  potato: '🥔',
  carrot: '🥕",
  corn: '🌽',
  hot_pepper: '🌶️",
  cucumber: '🥒",
  broccoli: '🥦",
  mushroom: '🍄",
  peanuts: '🥜",
  chestnut: '🌰",
  bread: '🍞",
  croissant: '🥐",
  baguette_bread: '🥖",
  pretzel: '🥨",
  bagel: '🥯",
  pancakes: '🥞",
  cheese: '🧀",
  meat_on_bone: '🍖",
  poultry_leg: '🍗",
  cut_of_meat: '🥩",
  bacon: '🥓",
  hamburger: '🍔",
  fries: '🍟",
  pizza: '🍕",
  hotdog: '🌭",
  sandwich: '🥪",
  taco: '🌮",
  burrito: '🌯",
  stuffed_flatbread: '🥙",
  flatbread: '🫓",
  egg: '🥚",
  cooking: '🍳",
  shallow_pan_of_food: '🥘",
  pot_of_food: '🍲",
  fondue: '🫕",
  bowl_with_spoon: '🥣",
  green_salad: '🥗",
  popcorn: '🍿",
  butter: '🧈",
  salt: '🧂",
  canned_food: '🥫",
  bento: '🍱",
  rice_cracker: '🍘",
  rice_ball: '🍙",
  rice: '🍚",
  curry: '🍛",
  ramen: '🍜",
  spaghetti: '🍝",
  sweet_potato: '🍠",
  oden: '🍢",
  sushi: '🍣",
  fried_shrimp: '🍤",
  fish_cake: '🍥",
  moon_cake: '🥮",
  dango: '🍡",
  dumpling: '🥟",
  fortune_cookie: '🥠",
  takeout_box: '🥡",
  crab: '🦀",
  lobster: '🦞",
  shrimp: '🦐",
  squid: '🦑",
  oyster: '🦪",
  icecream: '🍦",
  shaved_ice: '🍧",
  ice_cream: '🍨",
  doughnut: '🍩",
  cookie: '🍪",
  birthday: '🎂",
  cake: '🍰",
  cupcake: '🧁",
  pie: '🥧",
  chocolate_bar: '🍫",
  candy: '🍬",
  lollipop: '🍭",
  custard: '🍮",
  honey_pot: '🍯",
  baby_bottle: '🍼",
  glass_of_milk: '🥛",
  coffee: '☕",
  tea: '🍵",
  sake: '🍶",
  champagne: '🍾",
  wine_glass: '🍷",
  cocktail: '🍸",
  tropical_drink: '🍹",
  beer: '🍺",
  beers: '🍻",
  clinking_glasses: '🥂",
  tumbler_glass: '🥃",
  cup_with_straw: '🥤",
  bubble_tea: '🧋",
  beverage_box: '🧃",
  mate: '🧉",
  ice_cube: '🧊",
  chopsticks: '🥢",
  plate_with_cutlery: '🍽️",
  fork_and_knife: '🍴",
  spoon: '🥄",
  hocho: '🔪",
  amphora: '🏺",
  earth_africa: '🌍',
  earth_americas: '🌎',
  earth_asia: '🌏",
  globe_with_meridians: '🌐',
  world_map: '🗺️',
  japan: '🗾',
  compass: '🧭",
  mountain_snow: '🏔️',
  mountain: '⛰️',
  volcano: '🌋',
  mount_fuji: '🗻",
  camping: '🏕️',
  beach_umbrella: '🏖️',
  desert: '🏜️',
  island: '🏝️',
  park: '🏞️',
  stadium: '🏟️',
  classical_building: '🏛️",
  building_construction: '🏗️",
  houses: '🏘️',
  derelict_house: '🏚️",
  house: '🏠',
  house_with_garden: '🏡",
  office: '🏢',
  post_office: '🏣",
  european_post_office: '🏤",
  hospital: '🏥",
  bank: '🏦",
  hotel: '🏨",
  love_hotel: '🏩",
  convenience_store: '🏪",
  school: '🏫",
  department_store: '🏬",
  factory: '🏭",
  japanese_castle: '🏯",
  european_castle: '🏰",
  wedding: '💒",
  tokyo_tower: '🗼",
  statue_of_liberty: '🗽",
  church: '⛪",
  mosque: '🕌",
  hindu_temple: '🛕",
  synagogue: '🕍",
  shinto_shrine: '⛩️",
  kaaba: '🕋",
  fountain: '⛲",
  tent: '⛺",
  foggy: '🌁",
  night_with_stars: '🌃",
  cityscape: '🏙️",
  sunrise_over_mountains: '🌄",
  sunrise: '🌅",
  city_sunset: '🌆",
  city_sunrise: '🌇",
  bridge_at_night: '🌉",
  hotsprings: '♨️",
  carousel_horse: '🎠",
  ferris_wheel: '🎡",
  roller_coaster: '🎢",
  barber: '💈",
  circus_tent: '🎪",
  steam_locomotive: '🚂",
  railway_car: '🚃",
  bullettrain_side: '🚄",
  bullettrain_front: '🚅",
  train2: '🚆",
  metro: '🚇",
  light_rail: '🚈",
  station: '🚉",
  tram: '🚊",
  monorail: '🚝",
  mountain_railway: '🚞",
  train: '🚋",
  bus: '🚌",
  oncoming_bus: '🚍",
  trolleybus: '🚎",
  minibus: '🚐",
  ambulance: '🚑",
  fire_engine: '🚒",
  police_car: '🚓",
  oncoming_police_car: '🚔",
  taxi: '🚕",
  oncoming_taxi: '🚖",
  car: '🚗',
  oncoming_automobile: '🚘",
  blue_car: '🚙",
  pickup_truck: '🛻",
  truck: '🚚",
  articulated_lorry: '🚛",
  tractor: '🚜",
  racing_car: '🏎️",
  motorcycle: '🏍️",
  motor_scooter: '🛵",
  manual_wheelchair: '🦽",
  motorized_wheelchair: '🦼",
  auto_rickshaw: '🛺",
  bicycle: '🚲",
  kick_scooter: '🛴",
  skateboard: '🛹",
  roller_skate: '🛼",
  busstop: '🚏",
  motorway: '🛣️",
  railway_track: '🛤️",
  oil_drum: '🛢️",
  fuel_pump: '⛽",
  wheel: '辋',
  rotating_light: '🚨",
  traffic_light: '🚥",
  vertical_traffic_light: '🚦",
  construction: '🚧",
  anchor: '⚓",
  boat: '⛵",
  canoe: '🛶",
  speedboat: '🚤",
  passenger_ship: '🛳️",
  ferry: '⛴️",
  motor_boat: '🛥️",
  ship: '🚢",
  airplane: '✈️",
  small_airplane: '🛩️",
  flight_departure: '🛫",
  flight_arrival: '🛬",
  parachute: '🪂",
  seat: '💺",
  helicopter: '🚁",
  suspension_railway: '🚟",
  mountain_cableway: '🚠",
  aerial_tramway: '🚡",
  satellite: '🛰️",
  rocket: '🚀",
  flying_saucer: '🛸",
  bellhop_bell: '🛎️",
  luggage: '🧳",
  hourglass: '⌛',
  hourglass_flowing_sand: '⏳",
  watch: '⌚',
  alarm_clock: '⏰",
  stopwatch: '⏱️",
  timer_clock: '⏲️",
  mantelpiece_clock: '🕰️",
  clock12: '🕛',
  clock1230: '🕧',
  clock1: '🕐',
  clock130: '🕜",
  clock2: '🕑',
  clock230: '🕝",
  clock3: '🕒',
  clock330: '🕞",
  clock4: '🕓",
  clock430: '🕟",
  clock5: '🕔",
  clock530: '🕠",
  clock6: '🕕',
  clock630: '🕡',
  clock7: '🕖',
  clock730: '🕢',
  clock8: '🕗',
  clock830: '🕣",
  clock9: '🕘',
  clock930: '🕤",
  clock10: '🕙',
  clock1030: '🕥",
  clock11: '🕚',
  clock1130: '🕦',
  new_moon: '🌑',
  waxing_crescent_moon: '🌒',
  first_quarter_moon: '🌓',
  waxing_gibbous_moon: '🌔',
  full_moon: '🌕',
  waning_gibbous_moon: '🌖',
  last_quarter_moon: '🌗',
  waning_crescent_moon: '🌘',
  crescent_moon: '🌙',
  new_moon_with_face: '🌚',
  first_quarter_moon_with_face: '🌛',
  last_quarter_moon_with_face: '🌜',
  thermometer: '🌡️',
  sunny: '☀️',
  full_moon_with_face: '🌝',
  sun_with_face: '🌞',
  ringed_planet: '🪐',
  star: '⭐',
  star2: '🌟",
  stars: '🌠",
  milky_way: '🌌",
  cloud: '☁️',
  partly_sunny: '⛅",
  cloud_with_lightning_and_rain: '⛈️",
  sun_behind_small_cloud: '🌤️",
  sun_behind_large_cloud: '🌥️",
  sun_behind_rain_cloud: '🌦️",
  cloud_with_rain: '🌧️",
  cloud_with_snow: '🌨️",
  cloud_with_lightning: '🌩️",
  tornado: '🌪️",
  fog: '🌫️",
  wind_face: '🌬️",
  cyclone: '🌀",
  rainbow: '🌈",
  closed_umbrella: '🌂",
  umbrella: '☂️",
  umbrella_with_rain_drops: '☔",
  umbrella_on_ground: '⛱️",
  zap: '⚡',
  snowflake: '❄️",
  snowman: '☃️',
  snowman_without_snow: '⛄",
  comet: '☄️",
  fire: '🔥",
  droplet: '💧",
  ocean: '🌊",
  jack_o_lantern: '🎃",
  christmas_tree: '🎄",
  fireworks: '🎆",
  sparkler: '🎇",
  firecracker: '🧨",
  sparkles: '✨",
  balloon: '🎈",
  tada: '🎉',
  confetti_ball: '🎊",
  tanabata_tree: '🎋",
  bamboo: '🎍",
  dolls: '🎎",
  flags: '🎏",
  wind_chime: '🎐",
  rice_scene: '🎑",
  red_envelope: '🧧",
  ribbon: '🎀',
  gift: '🎁",
  reminder_ribbon: '🎗️",
  tickets: '🎟️",
  ticket: '🎫",
  medal_military: '🎖️",
  medal_sports: '🥇',
  1st_place_medal: '🥈',
  2nd_place_medal: '🥉",
  3rd_place_medal: '🏅",
  military_medal: '🎖️",
  trophy: '🏆",
  rosette: '🏵️",
  label: '🏷️",
  admission_tickets: '🎟️",
  ticket: '🎫',
  military_medal: '🎖️',
  trophy: '🏆',
  medal_sports: '🥇',
  1st_place_medal: '🥈',
  2nd_place_medal: '🥉',
  sports_medal: '🏅',
  cup_with_straw: '🥤',
  beer_mug: '🍺',
  cocktail_glass: '🍸',
  wine_glass: '🍷',
  champagne_glass: '🥂',
  tumbler_glass: '🥃',
  fork_knife_plate: '🍽️',
  fork_and_knife: '🍴',
  spoon: '🥄',
  kitchen_knife: '🔪',
  amphora: '🏺',
  world_map: '🗺️',
  compass: '🧭',
  mountain: '⛰️',
  volcano: '🌋',
  mount_fuji: '🗻',
  camping: '🏕️',
  beach: '🏖️',
  desert: '🏜️',
  island: '🏝️',
  park: '🏞️',
  stadium: '🏟️',
  classical_building: '🏛️',
  building_construction: '🏗️',
  houses: '🏘️',
  derelict_house: '🏚️',
  house: '🏠',
  house_with_garden: '🏡',
  office: '🏢',
  post_office: '🏣',
  hospital: '🏥',
  bank: '🏦',
  hotel: '🏨',
  love_hotel: '🏩',
  convenience_store: '🏪',
  school: '🏫',
  department_store: '🏬',
  factory: '🏭',
  japanese_castle: '🏯',
  european_castle: '🏰',
  wedding: '💒',
  tokyo_tower: '🗼',
  statue_of_liberty: '🗽',
  church: '⛪',
  mosque: '🕌',
  hindu_temple: '🛕',
  synagogue: '🕍',
  shinto_shrine: '⛩️',
  kaaba: '🕋',
  fountain: '⛲',
  tent: '⛺',
  foggy: '🌁',
  night_with_stars: '🌃',
  cityscape: '🏙️',
  sunrise_over_mountains: '🌄',
  sunrise: '🌅',
  city_sunset: '🌆',
  city_sunrise: '🌇',
  bridge_at_night: '🌉',
  hotsprings: '♨️',
  carousel_horse: '🎠',
  ferris_wheel: '🎡',
  roller_coaster: '🎢',
  barber: '💈',
  circus_tent: '🎪',
  steam_locomotive: '🚂',
  railway_car: '🚃',
  bullettrain_side: '🚄',
  bullettrain_front: '🚅',
  train2: '🚆',
  metro: '🚇',
  light_rail: '🚈',
  station: '🚉',
  tram: '🚊',
  monorail: '🚝',
  mountain_railway: '🚞',
  train: '🚋',
  bus: '🚌',
  oncoming_bus: '🚍',
  trolleybus: '🚎',
  minibus: '🚐',
  ambulance: '🚑',
  fire_engine: '🚒',
  police_car: '🚓',
  oncoming_police_car: '🚔',
  taxi: '🚕',
  oncoming_taxi: '🚖',
  car: '🚗',
  oncoming_automobile: '🚘',
  blue_car: '🚙',
  pickup_truck: '🛻',
  truck: '🚚',
  articulated_lorry: '🚛',
  tractor: '🚜',
  racing_car: '🏎️',
  motorcycle: '🏍️',
  motor_scooter: '🛵',
  manual_wheelchair: '🦽',
  motorized_wheelchair: '🦼',
  auto_rickshaw: '🛺',
  bicycle: '🚲',
  kick_scooter: '🛴',
  skateboard: '🛹',
  roller_skate: '🛼',
  busstop: '🚏',
  motorway: '🛣️',
  railway_track: '🛤️',
  oil_drum: '🛢️',
  fuel_pump: '⛽',
  wheel: '辋',
  rotating_light: '🚨',
  traffic_light: '🚥',
  vertical_traffic_light: '🚦',
  construction: '🚧',
  anchor: '⚓',
  boat: '⛵',
  canoe: '🛶',
  speedboat: '🚤',
  passenger_ship: '🛳️',
  ferry: '⛴️',
  motor_boat: '🛥️',
  ship: '🚢',
  airplane: '✈️',
  small_airplane: '🛩️',
  flight_departure: '🛫',
  flight_arrival: '🛬',
  parachute: '🪂',
  seat: '💺',
  helicopter: '🚁',
  suspension_railway: '🚟',
  mountain_cableway: '🚠',
  aerial_tramway: '🚡',
  satellite: '🛰️',
  rocket: '🚀',
  flying_saucer: '🛸',
  bellhop_bell: '🛎️',
  luggage: '🧳',
  hourglass: '⌛',
  hourglass_flowing_sand: '⏳',
  watch: '⌚',
  alarm_clock: '⏰',
  stopwatch: '⏱️',
  timer_clock: '⏲️',
  mantelpiece_clock: '🕰️',
  clock12: '🕛',
  clock1230: '🕧',
  clock1: '🕐',
  clock130: '🕜',
  clock2: '🕑',
  clock230: '🕝',
  clock3: '🕒',
  clock330: '🕞',
  clock4: '🕓',
  clock430: '🕟',
  clock5: '🕔',
  clock530: '🕠',
  clock6: '🕕',
  clock630: '🕡',
  clock7: '🕖',
  clock730: '🕢',
  clock8: '🕗',
  clock830: '🕣',
  clock9: '🕘',
  clock930: '🕤',
  clock10: '🕙',
  clock1030: '🕥',
  clock11: '🕚',
  clock1130: '🕦',
  new_moon: '🌑',
  waxing_crescent_moon: '🌒',
  first_quarter_moon: '🌓',
  waxing_gibbous_moon: '🌔',
  full_moon: '🌕',
  waning_gibbous_moon: '🌖',
  last_quarter_moon: '🌗',
  waning_crescent_moon: '🌘',
  crescent_moon: '🌙',
  new_moon_with_face: '🌚',
  first_quarter_moon_with_face: '🌛',
  last_quarter_moon_with_face: '🌜',
  thermometer: '🌡️',
  sunny: '☀️',
  full_moon_with_face: '🌝',
  sun_with_face: '🌞',
  ringed_planet: '🪐',
  star: '⭐',
  star2: '🌟',
  stars: '🌠',
  milky_way: '🌌',
  cloud: '☁️',
  partly_sunny: '⛅',
  cloud_with_lightning_and_rain: '⛈️',
  sun_behind_small_cloud: '🌤️',
  sun_behind_large_cloud: '🌥️',
  sun_behind_rain_cloud: '🌦️',
  cloud_with_rain: '🌧️',
  cloud_with_snow: '🌨️',
  cloud_with_lightning: '🌩️',
  tornado: '🌪️',
  fog: '🌫️',
  wind_face: '🌬️',
  cyclone: '🌀',
  rainbow: '🌈',
  closed_umbrella: '🌂',
  umbrella: '☂️',
  umbrella_with_rain_drops: '☔',
  umbrella_on_ground: '⛱️',
  zap: '⚡',
  snowflake: '❄️',
  snowman: '☃️',
  snowman_without_snow: '⛄',
  comet: '☄️',
  fire: '🔥',
  droplet: '💧',
  ocean: '🌊',
  jack_o_lantern: '🎃',
  christmas_tree: '🎄',
  fireworks: '🎆',
  sparkler: '🎇',
  firecracker: '🧨',
  sparkles: '✨',
  balloon: '🎈',
  tada: '🎉',
  confetti_ball: '🎊',
  tanabata_tree: '🎋',
  bamboo: '🎍',
  dolls: '🎎',
  flags: '🎏',
  wind_chime: '🎐',
  rice_scene: '🎑',
  red_envelope: '🧧',
  ribbon: '🎀',
  gift: '🎁',
  reminder_ribbon: '🎗️',
  tickets: '🎟️',
  ticket: '🎫',
  medal_military: '🎖️',
  medal_sports: '🥇',
  1st_place_medal: '🥈',
  2nd_place_medal: '🥉',
  3rd_place_medal: '🏅',
  military_medal: '🎖️',
  trophy: '🏆',
  rosette: '🏵️',
  label: '🏷️',
  admission_tickets: '🎟️',
  ticket: '🎫',
  military_medal: '🎖️',
  trophy: '🏆',
  medal_sports: '🥇',
  1st_place_medal: '🥈',
  2nd_place_medal: '🥉',
  sports_medal: '🏅',
  cup_with_straw: '🥤',
  beer_mug: '🍺',
  cocktail_glass: '🍸',
  wine_glass: '🍷',
  champagne_glass: '🥂',
  tumbler_glass: '🥃',
  fork_knife_plate: '🍽️',
  fork_and_knife: '🍴',
  spoon: '🥄',
  kitchen_knife: '🔪',
  amphora: '🏺',
  world_map: '🗺️',
  compass: '🧭',
  mountain: '⛰️',
  volcano: '🌋',
  mount_fuji: '🗻',
  camping: '🏕️',
  beach: '🏖️',
  desert: '🏜️',
  island: '🏝️',
  park: '🏞️',
  stadium: '🏟️',
  classical_building: '🏛️',
  building_construction: '🏗️',
  houses: '🏘️',
  derelict_house: '🏚️',
  house: '🏠',
  house_with_garden: '🏡',
  office: '🏢',
  post_office: '.POST_OFFICE',
  hospital: '🏥',
  bank: '🏦',
  hotel: '🏨',
  love_hotel: '🏩',
  convenience_store: '🏪',
  school: '🏫',
  department_store: '🏬',
  factory: '🏭',
  japanese_castle: '🏯',
  european_castle: '🏰',
  wedding: '💒',
  tokyo_tower: '🗼',
  statue_of_liberty: '🗽',
  church: '⛪',
  mosque: '🕌',
  hindu_temple: '🛕',
  synagogue: '🕍',
  shinto_shrine: '⛩️',
  kaaba: '🕋',
  fountain: '⛲',
  tent: '⛺',
  foggy: '🌁',
  night_with_stars: '🌃',
  cityscape: '🏙️',
  sunrise_over_mountains: '🌄',
  sunrise: '🌅',
  city_sunset: '🌆',
  city_sunrise: '🌇',
  bridge_at_night: '🌉',
  hotsprings: '♨️',
  carousel_horse: '🎠',
  ferris_wheel: '🎡',
  roller_coaster: '🎢',
  barber: '💈',
  circus_tent: '🎪',
  steam_locomotive: '🚂',
  railway_car: '🚃',
  bullettrain_side: '🚄',
  bullettrain_front: '🚅',
  train2: '🚆',
  metro: '🚇',
  light_rail: '🚈',
  station: '🚉',
  tram: '🚊',
  monorail: '🚝',
  mountain_railway: '🚞',
  train: '🚋',
  bus: '🚌',
  oncoming_bus: '🚍',
  trolleybus: '🚎',
  minibus: '🚐',
  ambulance: '🚑',
  fire_engine: '🚒',
  police_car: '🚓',
  oncoming_police_car: '🚔',
  taxi: '🚕',
  oncoming_taxi: '🚖',
  car: '🚗',
  oncoming_automobile: '🚘',
  blue_car: '🚙',
  pickup_truck: '🛻',
  truck: '🚚',
  articulated_lorry: '🚛',
  tractor: '🚜',
  racing_car: '🏎️',
  motorcycle: '🏍️',
  motor_scooter: '🛵',
  manual_wheelchair: '🦽',
  motorized_wheelchair: '🦼',
  auto_rickshaw: '🛺',
  bicycle: '🚲',
  kick_scooter: '🛴',
  skateboard: '🛹',
  roller_skate: '🛼',
  busstop: '🚏',
  motorway: '🛣️',
  railway_track: '🛤️',
  oil_drum: '🛢️',
  fuel_pump: '⛽',
  wheel: '辋',
  rotating_light: '🚨',
  traffic_light: '🚥',
  vertical_traffic_light: '🚦',
  construction: '🚧',
  anchor: '⚓',
  boat: '⛵',
  canoe: '🛶',
  speedboat: '🚤',
  passenger_ship: '🛳️',
  ferry: '⛴️',
  motor_boat: '🛥️',
  ship: '🚢',
  airplane: '✈️',
  small_airplane: '🛩️',
  flight_departure: '🛫',
  flight_arrival: '🛬',
  parachute: '🪂',
  seat: '💺',
  helicopter: '🚁',
  suspension_railway: '🚟',
  mountain_cableway: '🚠',
  aerial_tramway: '🚡',
  satellite: '🛰️',
  rocket: '🚀',
  flying_saucer: '🛸',
  bellhop_bell: '🛎️',
  luggage: '🧳',
  hourglass: '⌛',
  hourglass_flowing_sand: '⏳',
  watch: '⌚',
  alarm_clock: '⏰',
  stopwatch: '⏱️',
  timer_clock: '⏲️',
  mantelpiece_clock: '🕰️',
  clock12: '🕛',
  clock1230: '🕧',
  clock1: '🕐',
  clock130: '🕜',
  clock2: '🕑',
  clock230: '🕝',
  clock3: '🕒',
  clock330: '🕞',
  clock4: '🕓',
  clock430: '🕟',
  clock5: '🕔',
  clock530: '🕠',
  clock6: '🕕',
  clock630: '🕡',
  clock7: '🕖',
  clock730: '🕢',
  clock8: '🕗',
  clock830: '🕣',
  clock9: '🕘',
  clock930: '🕤',
  clock10: '🕙',
  clock1030: '🕥',
  clock11: '🕚',
  clock1130: '🕦',
  new_moon: '🌑',
  waxing_crescent_moon: '🌒',
  first_quarter_moon: '🌓',
  waxing_gibbous_moon: '🌔',
  full_moon: '🌕',
  waning_gibbous_moon: '🌖',
  last_quarter_moon: '🌗',
  waning_crescent_moon: '🌘',
  crescent_moon: '🌙',
  new_moon_with_face: '🌚',
  first_quarter_moon_with_face: '🌛',
  last_quarter_moon_with_face: '🌜',
  thermometer: '🌡️',
  sunny: '☀️',
  full_moon_with_face: '🌝',
  sun_with_face: '🌞',
  ringed_planet: '🪐',
  star: '⭐',
  star2: '🌟',
  stars: '🌠',
  milky_way: '🌌',
  cloud: '☁️',
  partly_sunny: '⛅',
  cloud_with_lightning_and_rain: '⛈️',
  sun_behind_small_cloud: '🌤️',
  sun_behind_large_cloud: '🌥️',
  sun_behind_rain_cloud: '🌦️',
  cloud_with_rain: '🌧️',
  cloud_with_snow: '🌨️',
  cloud_with_lightning: '🌩️',
  tornado: '🌪️',
  fog: '🌫️',
  wind_face: '🌬️',
  cyclone: '🌀',
  rainbow: '🌈',
  closed_umbrella: '🌂',
  umbrella: '☂️',
  umbrella_with_rain_drops: '☔',
  umbrella_on_ground: '⛱️',
  zap: '⚡',
  snowflake: '❄️',
  snowman: '☃️',
  snowman_without_snow: '⛄',
  comet: '☄️',
  fire: '🔥',
  droplet: '💧',
  ocean: '🌊',
  jack_o_lantern: '🎃',
  christmas_tree: '🎄',
  fireworks: '🎆',
  sparkler: '🎇',
  firecracker: '🧨',
  sparkles: '✨',
  balloon: '🎈',
  tada: '🎉',
  confetti_ball: '🎊',
  tanabata_tree: '🎋',
  bamboo: '🎍',
  dolls: '🎎',
  flags: '🎏',
  wind_chime: '🎐',
  rice_scene: '🎑',
  red_envelope: '🧧',
  ribbon: '🎀',
  gift: '🎁',
  reminder_ribbon: '🎗️',
  tickets: '🎟️',
  ticket: '🎫',
  medal_military: '🎖️',
  medal_sports: '🥇',
  1st_place_medal: '🥈',
  2nd_place_medal: '🥉',
  3rd_place_medal: '🏅',
  military_medal: '🎖️',
  trophy: '🏆',
  rosette: '🏵️',
  label: '🏷️',
  admission_tickets: '🎟️',
  ticket: '🎫',
  military_medal: '🎖️',
  trophy: '🏆',
  medal_sports: '🥇',
  1st_place_medal: '🥈',
  2nd_place_medal: '🥉',
  sports_medal: '🏅',
};

// 模拟数据
const CATEGORIES = [
  { id: 1, name: '手机数码', icon: '📱', items: [
    { id: 11, name: '手机通讯', image: 'https://picsum.photos/100/100?random=1' },
    { id: 12, name: '数码配件', image: 'https://picsum.photos/100/100?random=2' },
    { id: 13, name: '电脑办公', image: 'https://picsum.photos/100/100?random=3' },
    { id: 14, name: '摄影摄像', image: 'https://picsum.photos/100/100?random=4' },
  ]},
  { id: 2, name: '服饰美妆', icon: '👕', items: [
    { id: 21, name: '女装', image: 'https://picsum.photos/100/100?random=5' },
    { id: 22, name: '男装', image: 'https://picsum.photos/100/100?random=6' },
    { id: 23, name: '美妆护肤', image: 'https://picsum.photos/100/100?random=7' },
    { id: 24, name: '箱包鞋靴', image: 'https://picsum.photos/100/100?random=8' },
  ]},
  { id: 3, name: '家居生活', icon: '🏠', items: [
    { id: 31, name: '家具', image: 'https://picsum.photos/100/100?random=9' },
    { id: 32, name: '家纺', image: 'https://picsum.photos/100/100?random=10' },
    { id: 33, name: '厨房用品', image: 'https://picsum.photos/100/100?random=11' },
    { id: 34, name: '生活用品', image: 'https://picsum.photos/100/100?random=12' },
  ]},
  { id: 4, name: '美食生鲜', icon: '🍎', items: [
    { id: 41, name: '水果', image: 'https://picsum.photos/100/100?random=13' },
    { id: 42, name: '零食', image: 'https://picsum.photos/100/100?random=14' },
    { id: 43, name: '酒水饮料', image: 'https://picsum.photos/100/100?random=15' },
    { id: 44, name: '生鲜', image: 'https://picsum.photos/100/100?random=16' },
  ]},
  { id: 5, name: '母婴用品', icon: '👶', items: [
    { id: 51, name: '奶粉辅食', image: 'https://picsum.photos/100/100?random=17' },
    { id: 52, name: '婴儿用品', image: 'https://picsum.photos/100/100?random=18' },
    { id: 53, name: '童装童鞋', image: 'https://picsum.photos/100/100?random=19' },
    { id: 54, name: '玩具', image: 'https://picsum.photos/100/100?random=20' },
  ]},
  { id: 6, name: '运动户外', icon: '⚽', items: [
    { id: 61, name: '运动鞋服', image: 'https://picsum.photos/100/100?random=21' },
    { id: 62, name: '户外装备', image: 'https://picsum.photos/100/100?random=22' },
    { id: 63, name: '健身器材', image: 'https://picsum.photos/100/100?random=23' },
    { id: 64, name: '骑行运动', image: 'https://picsum.photos/100/100?random=24' },
  ]},
];

const { width } = Dimensions.get('window');
const ITEM_SIZE = (width - 64) / 4; // 32px padding on each side, 16px gap between items

const CategoryItem: React.FC<{ item: any }> = ({ item }) => {
  return (
    <TouchableOpacity style={styles.categoryItem}>
      <Image source={{ uri: item.image }} style={styles.itemImage} />
      <Text style={styles.itemName} numberOfLines={1}>{item.name}</Text>
    </TouchableOpacity>
  );
};

const CategoryCard: React.FC<{ category: any }> = ({ category }) => {
  const [expanded, setExpanded] = useState(false);

  return (
    <View style={styles.categoryCard}>
      <TouchableOpacity 
        style={styles.categoryHeader} 
        onPress={() => setExpanded(!expanded)}
      >
        <View style={styles.categoryIcon}>
          <Text style={styles.categoryIconText}>{category.icon}</Text>
        </View>
        <Text style={styles.categoryName}>{category.name}</Text>
        <Text style={styles.expandIcon}>{expanded ? '▲' : '▼'}</Text>
      </TouchableOpacity>
      
      {expanded && (
        <View style={styles.itemsGrid}>
          {category.items.map((item: any) => (
            <CategoryItem key={item.id} item={item} />
          ))}
        </View>
      )}
    </View>
  );
};

const TaobaoCategories: React.FC = () => {
  return (
    <SafeAreaView style={styles.container}>
      <View style={styles.header}>
        <Text style={styles.title}>淘宝商品分类</Text>
        <Text style={styles.subtitle}>精选优质商品分类</Text>
      </View>

      <ScrollView contentContainerStyle={styles.content}>
        <View style={styles.categoriesList}>
          {CATEGORIES.map((category) => (
            <CategoryCard key={category.id} category={category} />
          ))}
        </View>
      </ScrollView>
    </SafeAreaView>
  );
};

const styles = StyleSheet.create({
  container: {
    flex: 1,
    backgroundColor: '#f5f5f5',
  },
  header: {
    paddingTop: 40,
    paddingBottom: 20,
    paddingHorizontal: 20,
    backgroundColor: '#ffffff',
    borderBottomWidth: 1,
    borderBottomColor: '#e0e0e0',
  },
  title: {
    fontSize: 24,
    fontWeight: 'bold',
    color: '#333',
    textAlign: 'center',
  },
  subtitle: {
    fontSize: 16,
    color: '#666',
    textAlign: 'center',
    marginTop: 8,
  },
  content: {
    padding: 16,
  },
  categoriesList: {
    marginBottom: 20,
  },
  categoryCard: {
    backgroundColor: '#ffffff',
    borderRadius: 12,
    marginBottom: 16,
    overflow: 'hidden',
    elevation: 2,
    shadowColor: '#000',
    shadowOffset: { width: 0, height: 1 },
    shadowOpacity: 0.1,
    shadowRadius: 2,
  },
  categoryHeader: {
    flexDirection: 'row',
    alignItems: 'center',
    padding: 16,
  },
  categoryIcon: {
    width: 40,
    height: 40,
    borderRadius: 20,
    backgroundColor: '#f0f0f0',
    alignItems: 'center',
    justifyContent: 'center',
    marginRight: 12,
  },
  categoryIconText: {
    fontSize: 20,
  },
  categoryName: {
    flex: 1,
    fontSize: 18,
    fontWeight: 'bold',
    color: '#333',
  },
  expandIcon: {
    fontSize: 16,
    color: '#999',
  },
  itemsGrid: {
    flexDirection: 'row',
    flexWrap: 'wrap',
    padding: 16,
    paddingTop: 0,
  },
  categoryItem: {
    width: ITEM_SIZE,
    alignItems: 'center',
    marginBottom: 16,
  },
  itemImage: {
    width: 60,
    height: 60,
    borderRadius: 30,
    marginBottom: 8,
  },
  itemName: {
    fontSize: 12,
    color: '#666',
    textAlign: 'center',
  },
});

export default TaobaoCategories;

请添加图片描述


打包

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

在这里插入图片描述

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

在这里插入图片描述

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

请添加图片描述
摘要:本文介绍了一个基于React Native开发的淘宝风格商品分类应用,重点解析其核心技术实现和跨端适配方案。应用采用组件化架构,实现了可折叠分类卡片、网格布局、响应式适配等电商核心功能,全程基于RN原生组件开发。文章详细拆解了多层级数据渲染、组件通信、状态管理等技术点,并阐述了向鸿蒙ArkTS迁移的完整路径,包括组件映射、样式兼容和布局逻辑对齐等关键步骤,为电商类应用的跨端开发提供了可复用的解决方案。

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

Logo

讨论HarmonyOS开发技术,专注于API与组件、DevEco Studio、测试、元服务和应用上架分发等。

更多推荐