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


本项目采用React Native函数式组件架构,以WarehouseManagementApp为核心组件,实现了仓库管理的完整功能流程。架构设计遵循模块化原则,将数据结构、状态管理和业务逻辑清晰分离,便于维护和扩展。

核心技术栈

  • React Native:跨平台移动应用开发框架,支持iOS、Android和鸿蒙系统
  • TypeScript:提供类型安全,增强代码可维护性和开发体验
  • Hooks API:使用useState进行状态管理,简化组件逻辑
  • Flexbox:实现响应式布局,适配不同屏幕尺寸
  • Base64图标:内置图标资源,减少网络请求,提升加载速度
  • Dimensions API:获取屏幕尺寸,实现精细化布局控制

商品类型(Product)

type Product = {
  id: string;
  name: string;
  barcode: string;
  quantity: number;
  location: string;
  category: string;
};

该类型设计合理,包含了仓库管理所需的核心商品属性:

  • id:唯一标识符,确保数据唯一性
  • name:商品名称,便于识别
  • barcode:条形码,用于扫描识别
  • quantity:库存数量,核心管理指标
  • location:存放位置,便于库位管理
  • category:分类信息,便于商品归类

库存记录类型(InventoryRecord)

type InventoryRecord = {
  id: string;
  productId: string;
  operation: '入库' | '出库';
  quantity: number;
  timestamp: string;
  operator: string;
};

库存记录类型设计全面,包含了库存操作的关键信息:

  • id:记录唯一标识
  • productId:关联商品ID,建立数据关联
  • operation:操作类型,使用联合类型确保类型安全
  • quantity:操作数量,记录库存变动
  • timestamp:操作时间,用于追溯
  • operator:操作人,明确责任

库位类型(StorageLocation)

type StorageLocation = {
  id: string;
  code: string;
  capacity: number;
  currentStock: number;
  isAvailable: boolean;
};

库位类型设计合理,包含了仓库管理的必要信息:

  • id:库位唯一标识
  • code:库位编码,便于定位
  • capacity:容量信息,用于库存规划
  • currentStock:当前库存,实时监控
  • isAvailable:可用状态,确保有效分配

核心状态

应用使用useState钩子管理五个核心状态:

  • products:商品列表,支持动态更新
  • inventoryRecords:库存记录列表,支持动态更新
  • storageLocations:库位列表,使用常量状态
  • scannedBarcode:扫描的条形码,支持用户输入
  • newQuantity:新的数量信息,用于库存操作

状态更新

  1. 条形码扫描:通过handleScanBarcode函数,根据条形码查找商品信息
  2. 入库操作:通过handleStockIn函数,更新商品库存和库位信息
  3. 状态更新:通过setProducts和setInventoryRecords函数,更新商品和库存记录状态
  4. 用户输入:通过TextInput的onChangeText事件,实时更新scannedBarcode和newQuantity状态

状态管理

  • 不可变数据模式:使用扩展运算符(...)创建新状态,避免直接修改原状态
  • 数据验证:在进行库存操作前进行数据完整性检查
  • 状态清理:在操作完成后,清理临时状态,保持界面整洁
  • 批量更新:在状态更新时使用map方法批量处理,确保数据一致性

核心业务

  1. 商品管理:展示商品信息,包含库存数量和存放位置
  2. 条形码扫描:通过扫描条形码快速识别商品
  3. 入库操作:处理商品入库,包括数量输入和库位分配
  4. 库存记录:记录所有库存操作,便于追溯
  5. 库位管理:根据库位容量和可用性,智能分配存放位置

核心业务

const handleStockIn = (productId: string) => {
  Alert.prompt(
    '入库操作',
    '请输入入库数量:',
    [
      { text: '取消', style: 'cancel' },
      {
        text: '确定',
        onPress: (quantity) => {
          if (quantity && parseInt(quantity) > 0) {
            const product = products.find(p => p.id === productId);
            if (product) {
              // 分配库位
              const availableLocation = storageLocations.find(loc =>
                loc.isAvailable && loc.currentStock + parseInt(quantity) <= loc.capacity
              );
              
              if (availableLocation) {
                // 更新产品库存和位置
                setProducts(products.map(p =>
                  p.id === productId
                    ? {
                        ...p,
                        quantity: p.quantity + parseInt(quantity),
                        location: availableLocation.code
                      }
                    : p
                ));
                
                // 更新库位库存
                // 这里简化处理,实际应该更新storageLocations状态
                
                // 添加库存记录
                const record: InventoryRecord = {
                  id: (inventoryRecords.length + 1).toString(),
                  productId: productId,
                  operation: '入库',
                  quantity: parseInt(quantity),
                  timestamp: new Date().toLocaleString('zh-CN'),
                  operator: '当前用户'
                };
                setInventoryRecords([...inventoryRecords, record]);
                
                Alert.alert('成功', `商品已入库到${availableLocation.code}库位`);
                setScannedBarcode('');
                setNewQuantity('');
              } else {
                Alert.alert('提示', '没有合适的库位,请检查库存容量');
              }
            }
          }
        }
      }
    ],
    'plain-text'
  );
};

数据流

  • 单向数据流:状态 → 视图 → 用户操作 → 状态更新
  • 数据验证:在进行库存操作前进行数据完整性检查
  • 业务逻辑封装:将复杂计算逻辑封装在专用函数中,提高代码可读性
  • 用户交互反馈:使用Alert组件提供操作确认和信息提示,提升用户体验

组件

  • 核心组件:SafeAreaView、View、Text、ScrollView、TouchableOpacity、TextInput等在鸿蒙系统上有对应实现
  • API兼容性:Dimensions API在鸿蒙系统中可正常使用,确保布局适配
  • Alert组件:鸿蒙系统支持Alert组件的基本功能,但样式可能有所差异
  • Alert.prompt:需要注意鸿蒙系统对Alert.prompt的支持情况,可能需要自定义实现

资源

  • Base64图标:在鸿蒙系统中同样支持,可减少网络请求,提升性能
  • 内存管理:鸿蒙系统对内存使用更为严格,需注意资源释放
  • 计算性能:对于库位分配等操作,鸿蒙系统的处理性能与其他平台相当

性能

  1. 渲染性能

    • 避免不必要的重渲染,合理使用React.memo
    • 对于长商品列表,建议使用FlatList替代ScrollView
    • 优化组件结构,减少嵌套层级
  2. 数据处理

    • 缓存计算结果,避免重复计算
    • 优化库位分配算法,特别是可用库位的查找
    • 考虑使用useMemo缓存计算结果,提高性能
  3. 内存管理

    • 及时释放不再使用的资源
    • 避免内存泄漏,特别是在处理多个商品和库存记录时
    • 合理使用缓存策略,平衡性能和内存占用
  • 条件渲染:使用Platform API检测平台,针对不同平台使用不同实现
  • 样式适配:考虑鸿蒙系统的设计规范,调整UI样式以符合平台特性
  • 权限处理:鸿蒙系统的权限管理与Android有所不同,需单独处理
  • 鸿蒙特性:充分利用鸿蒙系统的分布式能力,实现多设备协同
  • 扫码功能:针对鸿蒙系统的扫码API,调整条形码扫描实现

1. 类型定义

  • 使用枚举类型:将操作类型、商品分类等使用枚举类型替代字符串,提高代码可读性和可维护性
  • 类型扩展:考虑使用接口继承,增强类型系统的表达能力
  • 类型守卫:添加更多类型守卫,确保运行时类型安全
  • 国际化支持:考虑添加国际化支持,特别是操作类型等术语

2. 状态管理

  • 状态分离:对于复杂状态,考虑使用useReducer或状态管理库(如Redux、Zustand)
  • 状态持久化:实现状态持久化,使用AsyncStorage存储商品和库存数据
  • 计算属性:使用useMemo缓存计算结果,避免重复计算
  • 批量操作:支持批量库存操作,提高管理效率

3. 组件化

  • 组件拆分:将大型组件拆分为更小的可复用组件,如ProductCard、InventoryRecordItem、StorageLocationCard等
  • 自定义Hook:提取重复的状态逻辑到自定义Hook,如useInventoryManager、useBarcodeScanner等
  • 高阶组件:使用高阶组件处理横切关注点,如错误边界、加载状态等
  • 表单组件:封装表单输入组件,提高代码复用性

4. 业务逻辑

  • 服务层分离:将业务逻辑分离到服务层,提高代码可测试性
  • 错误处理:增强错误处理机制,提供更友好的错误提示
  • 实时更新:考虑添加实时库存更新,提升管理效率
  • 智能算法:实现更智能的库位分配算法,考虑更多因素如商品分类、存取频率等

5. 性能

  • 列表优化:使用FlatList的性能优化特性,如getItemLayout、initialNumToRender等
  • 网络优化:实现请求防抖和节流,减少网络请求频率
  • Bundle优化:使用代码分割和Tree Shaking,减少应用体积
  • 启动优化:优化应用启动速度,减少初始加载时间

1. 代码

  • 模块化设计:采用模块化设计,分离平台特定代码
  • 配置管理:使用配置文件管理不同平台的差异
  • 目录结构:合理组织目录结构,便于维护和扩展
  • 安全分区:对于商品信息等敏感数据,采用安全分区存储

仓库管理作为供应链核心环节,要求应用具备条码识别的精准性、库存操作的高效性、库位管理的智能化三大核心能力。本文将深度拆解基于 React Native 开发的仓库管理应用,剖析其商品建模、入库流程、库位分配的核心技术实现,并提供完整的鸿蒙(HarmonyOS)ArkTS 跨端适配方案,为仓储类应用的跨端开发提供可落地的技术参考。

1. 仓储场景

仓库管理系统区别于普通应用的核心在于商品条码化、库存操作可追溯、库位资源动态化。代码通过 TypeScript 类型系统构建了精准贴合仓储管理场景的领域模型,充分体现了仓储管理的行业特性:

// 商品类型
type Product = {
  id: string;
  name: string;
  barcode: string;
  quantity: number;
  location: string;
  category: string;
};

// 库存记录类型
type InventoryRecord = {
  id: string;
  productId: string;
  operation: '入库' | '出库';
  quantity: number;
  timestamp: string;
  operator: string;
};

// 库位类型
type StorageLocation = {
  id: string;
  code: string;
  capacity: number;
  currentStock: number;
  isAvailable: boolean;
};

模型设计的仓储场景适配性分析

  • 商品维度条码化Product 模型以 barcode 作为核心标识,符合仓储管理"一物一码"的标准化管理要求,是扫码入库的基础;
  • 库存操作可追溯InventoryRecord 模型记录操作类型、数量、时间、操作人员等全维度信息,满足仓储管理的审计和追溯需求;
  • 操作类型约束化:采用"入库/出库"字面量类型约束,避免非法操作类型输入,保证库存操作的规范性;
  • 库位资源结构化StorageLocation 模型包含容量、当前库存、可用状态等核心维度,为智能库位分配提供数据支撑;
  • 空间管理精细化capacitycurrentStock 字段量化库位容量使用情况,是库存分配算法的核心依据;
  • 可用性状态化isAvailable 字段标记库位可用状态,保证库存分配的有效性,避免超容量入库;
  • 位置关联明确化:商品模型通过 location 字段关联库位编码,实现商品与库位的精准映射;
  • 类型安全保障:所有核心字段均有明确类型定义,避免数据类型错误导致的库存管理混乱;
  • 操作记录完整化:库存记录包含操作人、时间戳等元数据,满足仓储管理的合规性要求。

2. 仓储管理

仓储管理的核心痛点是条码识别的准确性、入库操作的高效性、库位分配的智能化,代码通过轻量化但专业的逻辑实现了仓储管理的核心业务流程:

(1)条码扫描

条码扫描是仓储入库的首要环节,必须确保条码识别的准确性和商品匹配的高效性:

const handleScanBarcode = () => {
  if (!scannedBarcode.trim()) {
    Alert.alert('提示', '请先扫描或输入条形码');
    return;
  }

  const product = products.find(p => p.barcode === scannedBarcode);
  if (product) {
    Alert.alert(
      '扫描结果',
      `商品: ${product.name}\n` +
      `当前库存: ${product.quantity}\n` +
      `存放位置: ${product.location}`,
      [
        { text: '取消', style: 'cancel' },
        { 
          text: '入库', 
          onPress: () => handleStockIn(product.id) 
        }
      ]
    );
  } else {
    Alert.alert('提示', '未找到对应商品,请检查条形码是否正确');
  }
};

条码扫描逻辑的仓储场景适配性

  • 空值校验前置:先校验条码输入是否为空,避免无效扫描操作,符合仓储操作的严谨性要求;
  • 精准商品匹配:通过条码精准匹配商品信息,保证入库操作的准确性,避免扫错码导致的库存错误;
  • 信息完整展示:扫描成功后展示商品名称、当前库存、存放位置等核心信息,为入库决策提供完整参考;
  • 操作流程引导:直接提供入库操作选项,简化操作流程,提升仓储作业效率;
  • 异常友好提示:未找到商品时给出明确提示,引导检查条码正确性,降低操作失误率;
  • 交互流程优化:采用弹窗形式展示扫描结果,符合移动端仓储操作的交互习惯;
  • 数据一致性保障:基于现有商品数据进行匹配,保证库存数据的一致性。
(2)智能入库

入库操作是仓储管理的核心环节,需基于库位容量和可用性实现最优分配,直接影响库存管理效率:

const handleStockIn = (productId: string) => {
  Alert.prompt(
    '入库操作',
    '请输入入库数量:',
    [
      { text: '取消', style: 'cancel' },
      {
        text: '确定',
        onPress: (quantity) => {
          if (quantity && parseInt(quantity) > 0) {
            const product = products.find(p => p.id === productId);
            if (product) {
              // 分配库位
              const availableLocation = storageLocations.find(loc => 
                loc.isAvailable && loc.currentStock + parseInt(quantity) <= loc.capacity
              );
              
              if (availableLocation) {
                // 更新产品库存和位置
                setProducts(products.map(p => 
                  p.id === productId 
                    ? { 
                        ...p, 
                        quantity: p.quantity + parseInt(quantity),
                        location: availableLocation.code
                      } 
                    : p
                ));
                
                // 更新库位库存
                // 这里简化处理,实际应该更新storageLocations状态
                
                // 添加库存记录
                const record: InventoryRecord = {
                  id: (inventoryRecords.length + 1).toString(),
                  productId: productId,
                  operation: '入库',
                  quantity: parseInt(quantity),
                  timestamp: new Date().toLocaleString('zh-CN'),
                  operator: '当前用户'
                };
                setInventoryRecords([...inventoryRecords, record]);
                
                Alert.alert('成功', `商品已入库到${availableLocation.code}库位`);
                setScannedBarcode('');
                setNewQuantity('');
              } else {
                Alert.alert('提示', '没有合适的库位,请检查库存容量');
              }
            }
          }
        }
      }
    ],
    'plain-text'
  );
};

入库与库位分配逻辑的智能化设计

  • 数量有效性校验:校验入库数量是否为正数,避免无效或负数入库,保证库存数据的准确性;
  • 智能库位筛选:自动筛选可用且容量充足的库位,避免超容量入库,符合仓储管理的容量管控要求;
  • 库存原子更新:采用不可变更新模式更新商品库存,避免直接修改原数据,保证数据一致性;
  • 位置自动关联:入库成功后自动更新商品的库位信息,实现商品与库位的动态绑定;
  • 操作记录完整化:自动生成入库操作记录,包含时间戳和操作人员,满足仓储审计需求;
  • 结果明确反馈:提示具体入库的库位编码,便于操作人员确认和后续查找;
  • 表单状态重置:入库成功后清空条码和数量输入,支持连续入库操作,提升作业效率;
  • 异常场景处理:无合适库位时给出明确提示,引导检查库位容量,避免操作阻塞;
  • 用户体验优化:采用弹窗输入数量的方式,符合移动端快速操作的交互习惯。
(3)手动录入

仓储管理中需支持手动录入补充,满足条码扫描不便或新增商品的入库需求:

const handleManualEntry = () => {
  if (!scannedBarcode.trim() || !newQuantity.trim()) {
    Alert.alert('提示', '请填写完整的商品信息');
    return;
  }

  const quantity = parseInt(newQuantity);
  if (isNaN(quantity) || quantity <= 0) {
    Alert.alert('提示', '请输入有效的数量');
    return;
  }

  const existingProduct = products.find(p => p.barcode === scannedBarcode);
  if (existingProduct) {
    // 更新现有商品
    setProducts(products.map(p => 
      p.id === existingProduct.id 
        ? { ...p, quantity: p.quantity + quantity } 
        : p
    ));
  } else {
    // 创建新商品
    const newProduct: Product = {
      id: (products.length + 1).toString(),
      name: `商品-${scannedBarcode.slice(-4)}`,
      barcode: scannedBarcode,
      quantity: quantity,
      location: '待分配',
      category: '未分类'
    };
    setProducts([...products, newProduct]);
  }

  // 添加库存记录
  const record: InventoryRecord = {
    id: (inventoryRecords.length + 1).toString(),
    productId: existingProduct?.id || (products.length + 1).toString(),
    operation: '入库',
    quantity: quantity,
    timestamp: new Date().toLocaleString('zh-CN'),
    operator: '当前用户'
  };
  setInventoryRecords([...inventoryRecords, record]);

  Alert.alert('成功', '库存已更新');
  setScannedBarcode('');
  setNewQuantity('');
};

手动录入逻辑的实用性设计

  • 全字段校验:校验条码和数量输入的完整性,保证入库信息的有效性;
  • 数量合法性校验:确保数量为有效正数,避免非法数值导致的库存错误;
  • 商品存在性判断:区分已有商品和新增商品,分别处理库存更新逻辑;
  • 存量商品更新:对已有商品仅更新库存数量,保留其他信息不变;
  • 新增商品创建:自动生成商品名称和基础信息,支持快速建档入库;
  • 状态标记明确:新增商品位置标记为"待分配",便于后续库位调整;
  • 操作记录统一化:无论新增还是更新,均生成标准化的库存操作记录;
  • 反馈机制完善:操作成功后给出明确提示,提升操作人员的确认感;
  • 表单重置优化:清空输入框,支持连续录入操作,提升批量入库效率;
  • 数据一致性保障:采用不可变更新模式,避免直接修改原数据导致的状态异常。

3. 仓储管理场景

仓储管理应用的视觉设计围绕效率性、辨识度、易用性三个核心维度展开,贴合仓储作业的业务特性和用户操作习惯:

(1)设计
  • 行业化色调体系:采用蓝色系(#0284c7/#0c4a6e)为主色调,契合仓储管理的专业、严谨的行业属性,传递高效、精准的品牌感知;
  • 功能模块卡片化:所有核心功能(扫码入库、手动录入、库存列表、库位状态)采用卡片式设计,符合移动端仓储管理的操作分区习惯;
  • 信息层级化展示:库存列表中核心信息(商品名称、库存数量)突出展示,详情信息(条码、位置)次级展示,提升信息读取效率;
  • 操作按钮场景化:不同功能模块展示对应操作按钮(扫码入库、确认入库),符合仓储管理的业务流程;
  • 库位状态可视化:通过颜色指示器直观展示库位可用状态,便于快速识别库位资源;
  • 操作类型差异化:入库操作记录以绿色标识,出库(预留)以红色标识,符合仓储管理的视觉识别习惯;
  • 底部导航场景化:按扫描、库存、库位、我的分类,贴合仓储管理的核心业务流程;
  • 安全区域适配:使用 SafeAreaView 适配异形屏,保证操作界面的完整性;
  • 卡片层级化设计:所有功能模块采用卡片式设计+轻微阴影,提升界面层次感,符合移动端管理系统的设计趋势。
(2)交互
  • 条码输入优化:扫码区域设计为图标+输入框组合,符合扫码操作的视觉认知;
  • 数字输入适配:数量输入框适配数字键盘,提升录入效率;
  • 列表滚动优化:库存列表、库位列表、操作记录均支持滚动,适配多商品/多库位管理的展示需求;
  • 操作反馈明确化:所有操作均提供明确的结果提示,保证操作人员的感知;
  • 底部导航固定展示:核心管理功能入口固定展示,便于操作过程中的功能快速切换;
  • 状态指示器直观化:库位可用状态通过圆形指示器展示,提升识别效率;
  • 操作记录倒序展示:最新的操作记录优先展示,符合仓储管理的查看习惯;
  • 库存数量突出化:库存数量以大号加粗字体展示,便于快速识别库存水平。

将 React Native 仓库管理系统迁移至鸿蒙平台,核心是基于 ArkTS + ArkUI 实现类型系统、状态管理、业务逻辑、视觉交互的对等还原,同时适配鸿蒙的组件特性和布局范式,保证仓储管理体验的一致性和专业性。

1. 架构

鸿蒙端适配遵循类型复用、逻辑对等、体验统一的原则,仓储管理的核心业务逻辑和视觉规范100%复用,仅需适配平台特有API和组件语法,确保仓库管理应用的跨端体验一致性:

@Entry
@Component
struct WarehouseManagementApp {
  // 类型定义:对等实现 TypeScript 类型 → 接口定义
  interface Product {
    id: string;
    name: string;
    barcode: string;
    quantity: number;
    location: string;
    category: string;
  }

  interface InventoryRecord {
    id: string;
    productId: string;
    operation: '入库' | '出库';
    quantity: number;
    timestamp: string;
    operator: string;
  }

  interface StorageLocation {
    id: string;
    code: string;
    capacity: number;
    currentStock: number;
    isAvailable: boolean;
  }

  // 状态管理:对等实现 useState → @State
  @State products: Product[] = [/* 初始商品数据 */];
  @State inventoryRecords: InventoryRecord[] = [/* 初始库存记录 */];
  @State storageLocations: StorageLocation[] = [/* 初始库位数据 */];
  @State scannedBarcode: string = '';
  @State newQuantity: string = '';

  // 业务逻辑:完全复用 RN 端的仓储管理核心逻辑
  handleScanBarcode(): void {/* 条码扫描 */}
  handleStockIn(productId: string): void {/* 入库操作 */}
  handleManualEntry(): void {/* 手动录入 */}

  // 页面构建:镜像 RN 端布局结构,适配鸿蒙组件特性
  build() {
    Column() {
      // 头部区域
      // 扫码入库区
      // 手动录入区
      // 库存列表
      // 库位状态
      // 操作记录
      // 底部导航
    }
  }
}

React Native 特性 鸿蒙 ArkUI 对应实现 仓储场景适配关键说明
TypeScript 类型定义 TypeScript 接口定义 商品、库存记录、库位等类型完全复用,保证仓储管理数据结构一致性
useState @State 装饰器 商品列表、库存记录、表单状态等管理逻辑完全复用,保持仓储流程一致性
入库算法 函数逻辑完全复用 库位分配、库存更新、操作记录规则一致,保证仓储管理的准确性
TextInput TextInput 组件 输入框属性适配,数字输入类型对等实现,保持录入体验一致
TouchableOpacity Button + onClick 所有可点击区域通过 onClick 事件实现,保持操作交互一致性
Alert.alert AlertDialog.show 条码扫描、入库操作等弹窗逻辑对等,符合仓储管理操作习惯
StyleSheet 链式样式 蓝色系主色调、商品卡片样式等视觉规范100%复用
Array.map ForEach 组件 商品/库存/库位列表渲染逻辑一致,适配多商品/多库位展示需求
ScrollView Scroll 组件 滚动容器语法差异,功能一致,适配长列表展示
状态颜色映射 函数逻辑完全复用 库位状态-颜色映射规则一致,保证仓储管理状态的视觉认知一致性
底部导航 Position.Fixed 导航栏定位语法差异,效果一致,保证仓储管理操作的便捷性

3. 鸿蒙代码

// 鸿蒙 ArkTS 完整实现 - 仓库管理系统
@Entry
@Component
struct WarehouseManagementApp {
  // 类型定义
  interface Product {
    id: string;
    name: string;
    barcode: string;
    quantity: number;
    location: string;
    category: string;
  }

  interface InventoryRecord {
    id: string;
    productId: string;
    operation: '入库' | '出库';
    quantity: number;
    timestamp: string;
    operator: string;
  }

  interface StorageLocation {
    id: string;
    code: string;
    capacity: number;
    currentStock: number;
    isAvailable: boolean;
  }

  // 状态管理
  @State products: Product[] = [
    {
      id: '1',
      name: 'iPhone 15 Pro',
      barcode: '123456789012',
      quantity: 50,
      location: 'A01-01',
      category: '电子产品'
    },
    {
      id: '2',
      name: 'MacBook Air',
      barcode: '234567890123',
      quantity: 30,
      location: 'A02-05',
      category: '电脑设备'
    }
  ];

  @State inventoryRecords: InventoryRecord[] = [
    {
      id: '1',
      productId: '1',
      operation: '入库',
      quantity: 20,
      timestamp: '2023-12-01 10:30',
      operator: '张管理员'
    }
  ];

  @State storageLocations: StorageLocation[] = [
    {
      id: 'loc1',
      code: 'A01-01',
      capacity: 100,
      currentStock: 50,
      isAvailable: true
    },
    {
      id: 'loc2',
      code: 'A02-05',
      capacity: 80,
      currentStock: 30,
      isAvailable: true
    }
  ];

  @State scannedBarcode: string = '';
  @State newQuantity: string = '';

  // 处理条码扫描
  handleScanBarcode(): void {
    if (!this.scannedBarcode.trim()) {
      AlertDialog.show({
        title: '提示',
        message: '请先扫描或输入条形码',
        confirm: { value: '确定' }
      });
      return;
    }

    const product = this.products.find(p => p.barcode === this.scannedBarcode);
    if (product) {
      AlertDialog.show({
        title: '扫描结果',
        message: `商品: ${product.name}\n当前库存: ${product.quantity}\n存放位置: ${product.location}`,
        buttons: [
          { value: '取消', action: () => {} },
          { 
            value: '入库', 
            action: () => this.handleStockIn(product.id) 
          }
        ]
      });
    } else {
      AlertDialog.show({
        title: '提示',
        message: '未找到对应商品,请检查条形码是否正确',
        confirm: { value: '确定' }
      });
    }
  }

  // 处理入库操作
  handleStockIn(productId: string): void {
    TextPrompt.show({
      title: '入库操作',
      message: '请输入入库数量:',
      confirm: {
        value: '确定',
        action: (quantity: string) => {
          if (quantity && parseInt(quantity) > 0) {
            const product = this.products.find(p => p.id === productId);
            if (product) {
              // 分配库位
              const availableLocation = this.storageLocations.find(loc => 
                loc.isAvailable && loc.currentStock + parseInt(quantity) <= loc.capacity
              );
              
              if (availableLocation) {
                // 更新产品库存和位置
                this.products = this.products.map(p => 
                  p.id === productId 
                    ? { 
                        ...p, 
                        quantity: p.quantity + parseInt(quantity),
                        location: availableLocation.code
                      } 
                    : p
                );
                
                // 添加库存记录
                const record: InventoryRecord = {
                  id: (this.inventoryRecords.length + 1).toString(),
                  productId: productId,
                  operation: '入库',
                  quantity: parseInt(quantity),
                  timestamp: new Date().toLocaleString('zh-CN'),
                  operator: '当前用户'
                };
                this.inventoryRecords = [...this.inventoryRecords, record];
                
                AlertDialog.show({
                  title: '成功',
                  message: `商品已入库到${availableLocation.code}库位`,
                  confirm: { value: '确定' }
                });
                this.scannedBarcode = '';
                this.newQuantity = '';
              } else {
                AlertDialog.show({
                  title: '提示',
                  message: '没有合适的库位,请检查库存容量',
                  confirm: { value: '确定' }
                });
              }
            }
          }
        }
      },
      cancel: { value: '取消' }
    });
  }

  // 处理手动录入
  handleManualEntry(): void {
    if (!this.scannedBarcode.trim() || !this.newQuantity.trim()) {
      AlertDialog.show({
        title: '提示',
        message: '请填写完整的商品信息',
        confirm: { value: '确定' }
      });
      return;
    }

    const quantity = parseInt(this.newQuantity);
    if (isNaN(quantity) || quantity <= 0) {
      AlertDialog.show({
        title: '提示',
        message: '请输入有效的数量',
        confirm: { value: '确定' }
      });
      return;
    }

    const existingProduct = this.products.find(p => p.barcode === this.scannedBarcode);
    if (existingProduct) {
      // 更新现有商品
      this.products = this.products.map(p => 
        p.id === existingProduct.id 
          ? { ...p, quantity: p.quantity + quantity } 
          : p
      );
    } else {
      // 创建新商品
      const newProduct: Product = {
        id: (this.products.length + 1).toString(),
        name: `商品-${this.scannedBarcode.slice(-4)}`,
        barcode: this.scannedBarcode,
        quantity: quantity,
        location: '待分配',
        category: '未分类'
      };
      this.products = [...this.products, newProduct];
    }

    // 添加库存记录
    const record: InventoryRecord = {
      id: (this.inventoryRecords.length + 1).toString(),
      productId: existingProduct?.id || (this.products.length + 1).toString(),
      operation: '入库',
      quantity: quantity,
      timestamp: new Date().toLocaleString('zh-CN'),
      operator: '当前用户'
    };
    this.inventoryRecords = [...this.inventoryRecords, record];

    AlertDialog.show({
      title: '成功',
      message: '库存已更新',
      confirm: { value: '确定' }
    });
    this.scannedBarcode = '';
    this.newQuantity = '';
  }

  build() {
    Column()
      .flex(1)
      .backgroundColor('#f0f9ff')
      .safeArea(true) {
      
      // 头部区域
      Column()
        .padding(16)
        .backgroundColor('#ffffff')
        .borderBottom({ width: 1, color: '#bae6fd' }) {
        Text('仓库管理')
          .fontSize(20)
          .fontWeight(FontWeight.Bold)
          .fontColor('#0c4a6e')
          .marginBottom(4);
        
        Text('扫码入库,智能管理')
          .fontSize(14)
          .fontColor('#0284c7');
      }

      // 滚动内容区
      Scroll()
        .flex(1)
        .marginTop(12) {
        Column() {
          // 条码扫描卡片
          Column()
            .backgroundColor('#ffffff')
            .marginLeft(16)
            .marginRight(16)
            .marginBottom(12)
            .borderRadius(12)
            .padding(16)
            .shadow({ color: '#000', offsetX: 0, offsetY: 2, opacity: 0.1, radius: 4 }) {
            
            Text('扫描入库')
              .fontSize(16)
              .fontWeight(FontWeight.SemiBold)
              .fontColor('#0c4a6e')
              .marginBottom(12);
            
            // 扫描区域
            Row()
              .alignItems(ItemAlign.Center)
              .backgroundColor('#f0f9ff')
              .borderRadius(8)
              .paddingLeft(12)
              .paddingRight(12)
              .marginBottom(12) {
              Text('📷')
                .fontSize(20)
                .fontColor('#64748b')
                .marginRight(8);
              
              TextInput({
                placeholder: '扫描或输入条形码',
                text: this.scannedBarcode
              })
                .flexGrow(1)
                .paddingTop(12)
                .paddingBottom(12)
                .fontSize(14)
                .fontColor('#0c4a6e')
                .onChangeText((text) => {
                  this.scannedBarcode = text;
                });
            }
            
            // 扫描按钮
            Button('扫描条码')
              .backgroundColor('#0284c7')
              .paddingTop(14)
              .paddingBottom(14)
              .borderRadius(8)
              .fontSize(16)
              .fontWeight(FontWeight.SemiBold)
              .fontColor(Color.White)
              .onClick(() => this.handleScanBarcode());
          }

          // 手动录入卡片
          Column()
            .backgroundColor('#ffffff')
            .marginLeft(16)
            .marginRight(16)
            .marginBottom(12)
            .borderRadius(12)
            .padding(16)
            .shadow({ color: '#000', offsetX: 0, offsetY: 2, opacity: 0.1, radius: 4 }) {
            
            Text('手动录入')
              .fontSize(16)
              .fontWeight(FontWeight.SemiBold)
              .fontColor('#0c4a6e')
              .marginBottom(12);
            
            // 数量输入框
            TextInput({
              placeholder: '入库数量',
              text: this.newQuantity
            })
              .borderWidth(1)
              .borderColor('#bae6fd')
              .borderRadius(8)
              .padding(12)
              .fontSize(14)
              .backgroundColor('#f0f9ff')
              .marginBottom(12)
              .type(InputType.Number)
              .onChangeText((text) => {
                this.newQuantity = text;
              });
            
            // 确认入库按钮
            Button('确认入库')
              .backgroundColor('#10b981')
              .paddingTop(14)
              .paddingBottom(14)
              .borderRadius(8)
              .fontSize(16)
              .fontWeight(FontWeight.SemiBold)
              .fontColor(Color.White)
              .onClick(() => this.handleManualEntry());
          }

          // 库存列表卡片
          Column()
            .backgroundColor('#ffffff')
            .marginLeft(16)
            .marginRight(16)
            .marginBottom(12)
            .borderRadius(12)
            .padding(16)
            .shadow({ color: '#000', offsetX: 0, offsetY: 2, opacity: 0.1, radius: 4 }) {
            
            Text('当前库存')
              .fontSize(16)
              .fontWeight(FontWeight.SemiBold)
              .fontColor('#0c4a6e')
              .marginBottom(12);
            
            // 商品列表
            ForEach(this.products, (product: Product) => {
              Row()
                .justifyContent(FlexAlign.SpaceBetween)
                .alignItems(ItemAlign.Center)
                .paddingTop(12)
                .paddingBottom(12)
                .borderBottom({ width: 1, color: '#bae6fd' }) {
              
              // 商品信息
              Column()
                .flexGrow(1) {
                Text(product.name)
                  .fontSize(14)
                  .fontWeight(FontWeight.Medium)
                  .fontColor('#0c4a6e')
                  .marginBottom(2);
                
                Text(`条码: ${product.barcode}`)
                  .fontSize(12)
                  .fontColor('#64748b')
                  .marginBottom(2);
                
                Text(`位置: ${product.location}`)
                  .fontSize(12)
                  .fontColor('#64748b');
              }
              
              // 库存信息
              Column()
                .alignItems(ItemAlign.Center) {
                Text(`${product.quantity}`)
                  .fontSize(18)
                  .fontWeight(FontWeight.Bold)
                  .fontColor('#0284c7');
                
                Text('件')
                  .fontSize(12)
                  .fontColor('#64748b');
              }
            }
            })
          }

          // 库位状态卡片
          Column()
            .backgroundColor('#ffffff')
            .marginLeft(16)
            .marginRight(16)
            .marginBottom(12)
            .borderRadius(12)
            .padding(16)
            .shadow({ color: '#000', offsetX: 0, offsetY: 2, opacity: 0.1, radius: 4 }) {
            
            Text('库位状态')
              .fontSize(16)
              .fontWeight(FontWeight.SemiBold)
              .fontColor('#0c4a6e')
              .marginBottom(12);
            
            // 库位列表
            ForEach(this.storageLocations, (location: StorageLocation) => {
              Row()
                .justifyContent(FlexAlign.SpaceBetween)
                .alignItems(ItemAlign.Center)
                .paddingTop(12)
                .paddingBottom(12)
                .borderBottom({ width: 1, color: '#bae6fd' }) {
              
              // 库位信息
              Column()
                .flexGrow(1) {
                Text(location.code)
                  .fontSize(14)
                  .fontWeight(FontWeight.Medium)
                  .fontColor('#0c4a6e')
                  .marginBottom(2);
                
                Text(`容量: ${location.currentStock}/${location.capacity}`)
                  .fontSize(12)
                  .fontColor('#64748b');
              }
              
              // 库位状态
              Column()
                .alignItems(ItemAlign.Center) {
                Column()
                  .width(12)
                  .height(12)
                  .borderRadius(6)
                  .backgroundColor(location.isAvailable ? '#10b981' : '#6b7280')
                  .marginBottom(4);
                
                Text(location.isAvailable ? '可用' : '已满')
                  .fontSize(12)
                  .fontColor(Color.White);
              }
            }
            })
          }

          // 操作记录卡片
          Column()
            .backgroundColor('#ffffff')
            .marginLeft(16)
            .marginRight(16)
            .marginBottom(12)
            .borderRadius(12)
            .padding(16)
            .shadow({ color: '#000', offsetX: 0, offsetY: 2, opacity: 0.1, radius: 4 }) {
            
            Text('操作记录')
              .fontSize(16)
              .fontWeight(FontWeight.SemiBold)
              .fontColor('#0c4a6e')
              .marginBottom(12);
            
            // 记录列表(取最近5条,倒序)
            ForEach(this.inventoryRecords.slice(-5).reverse(), (record: InventoryRecord) => {
              const product = this.products.find(p => p.id === record.productId);
              return Column()
                .paddingTop(12)
                .paddingBottom(12)
                .borderBottom({ width: 1, color: '#bae6fd' }) {
              
              // 记录头部
              Row()
                .justifyContent(FlexAlign.SpaceBetween)
                .alignItems(ItemAlign.Center)
                .marginBottom(4) {
                Text(product?.name || '未知商品')
                  .fontSize(14)
                  .fontWeight(FontWeight.Medium)
                  .fontColor('#0c4a6e');
                
                Text(record.operation)
                  .fontSize(12)
                  .fontWeight(FontWeight.Medium)
                  .fontColor(record.operation === '入库' ? '#10b981' : '#ef4444');
              }
              
              // 记录详情
              Row()
                .justifyContent(FlexAlign.SpaceBetween) {
                Text(`数量: ${record.quantity}`)
                  .fontSize(12)
                  .fontColor('#64748b');
                
                Text(record.timestamp)
                  .fontSize(12)
                  .fontColor('#64748b');
              }
            }
            })
          }

          // 使用说明卡片
          Column()
            .backgroundColor('#ffffff')
            .marginLeft(16)
            .marginRight(16)
            .marginBottom(80)
            .borderRadius(12)
            .padding(16)
            .shadow({ color: '#000', offsetX: 0, offsetY: 2, opacity: 0.1, radius: 4 }) {
            
            Text('使用说明')
              .fontSize(16)
              .fontWeight(FontWeight.SemiBold)
              .fontColor('#0c4a6e')
              .marginBottom(12);
            
            Text('• 扫描商品条码快速入库')
              .fontSize(14)
              .fontColor('#64748b')
              .lineHeight(20)
              .marginBottom(4);
            
            Text('• 系统自动分配最优库位')
              .fontSize(14)
              .fontColor('#64748b')
              .lineHeight(20)
              .marginBottom(4);
            
            Text('• 实时更新库存数据')
              .fontSize(14)
              .fontColor('#64748b')
              .lineHeight(20)
              .marginBottom(4);
            
            Text('• 支持手动录入补充')
              .fontSize(14)
              .fontColor('#64748b')
              .lineHeight(20);
          }
        }
      }

      // 底部导航
      Row()
        .justifyContent(FlexAlign.SpaceAround)
        .backgroundColor('#ffffff')
        .borderTop({ width: 1, color: '#bae6fd' })
        .paddingTop(12)
        .paddingBottom(12)
        .position(Position.Fixed)
        .bottom(0)
        .width('100%') {
      
      // 扫描
      Column()
        .alignItems(ItemAlign.Center)
        .flexGrow(1) {
        Text('📱')
          .fontSize(20)
          .fontColor('#94a3b8')
          .marginBottom(4);
        
        Text('扫描')
          .fontSize(12)
          .fontColor('#94a3b8');
      }
      
      // 库存
      Column()
        .alignItems(ItemAlign.Center)
        .flexGrow(1) {
        Text('📦')
          .fontSize(20)
          .fontColor('#94a3b8')
          .marginBottom(4);
        
        Text('库存')
          .fontSize(12)
          .fontColor('#94a3b8');
      }
      
      // 库位
      Column()
        .alignItems(ItemAlign.Center)
        .flexGrow(1) {
        Text('📍')
          .fontSize(20)
          .fontColor('#94a3b8')
          .marginBottom(4);
        
        Text('库位')
          .fontSize(12)
          .fontColor('#94a3b8');
      }
      
      // 我的(激活状态)
      Column()
        .alignItems(ItemAlign.Center)
        .flexGrow(1)
        .paddingTop(4)
        .borderTop({ width: 2, color: '#0284c7' }) {
        Text('👤')
          .fontSize(20)
          .fontColor('#0284c7')
          .marginBottom(4);
        
        Text('我的')
          .fontSize(12)
          .fontColor('#0284c7')
          .fontWeight(FontWeight.Medium);
      }
    }
  }
}

1. 原则

  • 仓储模型专业化复用:商品、库存记录、库位等类型定义在两端保持一致,包含条码、容量、可用状态等核心字段,保证仓储管理的专业性;
  • 视觉规范100%对齐:蓝色系主色调、商品卡片样式、库位状态颜色映射等视觉属性完全复用,符合仓储管理的行业视觉认知;
  • 业务逻辑统一:条码扫描、入库操作、库位分配、手动录入等核心逻辑保持一致,保证仓储管理流程的准确性和高效性;
  • 交互体验一致:条码输入、数量录入、按钮操作、弹窗提示等交互逻辑保持一致,降低仓储操作人员的学习成本;
  • 行业特性兼容:条码识别、库位分配、库存追溯等仓储行业特性在两端完整实现,满足仓储管理的核心需求;
  • 平台特性适配:弹窗展示、输入框类型、点击事件等平台特有API做适配处理,保证仓库管理应用的可用性;
  • 性能优化适配:鸿蒙端利用 ForEach 组件的复用机制优化列表渲染性能,RN 端利用数组映射实现高效渲染,均满足大规模库存管理的性能要求。

2. 仓库管理系统

  • 条码扫描原生集成:对接设备相机和条码扫描SDK,实现原生扫码功能,提升条码识别效率和准确性;
  • 批量操作支持:增加批量入库、批量移库、批量盘点功能,提升仓储作业效率;
  • 库存预警机制:设置库存上下限,实现库存不足或超储自动预警,优化库存水平;
  • 库位可视化:实现库位二维/三维可视化展示,支持拖拽式库位调整;
  • 出入库规则引擎:配置化出入库规则,支持先进先出、后进先出等多种出库策略;
  • 盘点功能完善:增加库存盘点、差异调整、盘点报告生成功能,满足周期性盘点需求;
  • 数据同步优化:实现离线操作和云端同步,支持网络不稳定环境下的仓储作业;
  • 报表分析功能:增加库存周转率、库位利用率、出入库频次等数据分析报表;
  • 权限精细化管理:区分管理员、操作员、盘点员等角色,提供精细化的权限控制;
  • 电子标签集成:对接RFID/电子标签系统,实现库存的自动化识别和管理;
  • 供应商管理:关联供应商信息,实现入库商品的溯源管理;
  • 订单关联:对接订单系统,实现按订单拣货、出库的自动化流程;
  • 移动打印支持:对接便携打印机,实现库位标签、拣货单的现场打印。

仓库管理系统作为供应链核心数字化工具,其跨端适配的关键在于条码识别精准性、库存操作高效性、体验一致性的三位一体。这份 React Native 实现的仓库管理组件,通过强类型领域建模、智能库位分配逻辑、行业化视觉设计,构建了高效的移动端仓储管理体验;而鸿蒙 ArkTS 端的适配实现,则验证了跨端开发中"逻辑复用、体验统一"的核心原则。

  1. 仓库管理系统的类型设计需包含商品条码、库存操作记录、库位容量/可用状态等核心字段,保证仓储管理的专业性;
  2. 条码扫描逻辑需包含空值校验、精准商品匹配、完整信息展示等核心步骤,保证条码识别的准确性和入库操作的高效性;
  3. 入库算法需基于库位可用性和容量约束实现智能分配,支持手动录入补充,最大化提升仓储作业效率;
  4. 视觉设计采用蓝色系主色调,功能模块采用卡片式设计,符合仓储管理的专业、严谨的行业视觉认知;
  5. 跨端适配的核心是仓储管理的行业逻辑复用 + 平台特性适配,无需重写核心业务逻辑;
  6. 条码输入设计需贴合仓储操作习惯,支持扫码和手动输入两种模式,提升入库操作的灵活性;
  7. 库存记录需包含完整的操作元数据,满足仓储管理的可追溯性和合规性要求;
  8. 库位列表需展示编码、容量使用情况、可用状态等核心信息,为库位分配决策提供数据支撑。

真实演示案例代码:




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

// Base64 图标库
const ICONS_BASE64 = {
  barcode: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNkYPhfDwAChwGA60e6kgAAAABJRU5ErkJggg==',
  warehouse: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNkYPhfDwAChwGA60e6kgAAAABJRU5ErkJggg==',
  location: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNkYPhfDwAChwGA60e6kgAAAABJRU5ErkJggg==',
  inventory: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNkYPhfDwAChwGA60e6kgAAAABJRU5ErkJggg==',
  scan: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNkYPhfDwAChwGA60e6kgAAAABJRU5ErkJggg==',
  check: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNkYPhfDwAChwGA60e6kgAAAABJRU5ErkJggg==',
  alert: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNkYPhfDwAChwGA60e6kgAAAABJRU5ErkJggg==',
  info: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNkYPhfDwAChwGA60e6kgAAAABJRU5ErkJggg==',
};

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

// 商品类型
type Product = {
  id: string;
  name: string;
  barcode: string;
  quantity: number;
  location: string;
  category: string;
};

// 库存记录类型
type InventoryRecord = {
  id: string;
  productId: string;
  operation: '入库' | '出库';
  quantity: number;
  timestamp: string;
  operator: string;
};

// 库位类型
type StorageLocation = {
  id: string;
  code: string;
  capacity: number;
  currentStock: number;
  isAvailable: boolean;
};

// 仓库管理应用组件
const WarehouseManagementApp: React.FC = () => {
  const [products, setProducts] = useState<Product[]>([
    {
      id: '1',
      name: 'iPhone 15 Pro',
      barcode: '123456789012',
      quantity: 50,
      location: 'A01-01',
      category: '电子产品'
    },
    {
      id: '2',
      name: 'MacBook Air',
      barcode: '234567890123',
      quantity: 30,
      location: 'A02-05',
      category: '电脑设备'
    }
  ]);

  const [inventoryRecords, setInventoryRecords] = useState<InventoryRecord[]>([
    {
      id: '1',
      productId: '1',
      operation: '入库',
      quantity: 20,
      timestamp: '2023-12-01 10:30',
      operator: '张管理员'
    }
  ]);

  const [storageLocations] = useState<StorageLocation[]>([
    {
      id: 'loc1',
      code: 'A01-01',
      capacity: 100,
      currentStock: 50,
      isAvailable: true
    },
    {
      id: 'loc2',
      code: 'A02-05',
      capacity: 80,
      currentStock: 30,
      isAvailable: true
    }
  ]);

  const [scannedBarcode, setScannedBarcode] = useState('');
  const [newQuantity, setNewQuantity] = useState('');

  const handleScanBarcode = () => {
    if (!scannedBarcode.trim()) {
      Alert.alert('提示', '请先扫描或输入条形码');
      return;
    }

    const product = products.find(p => p.barcode === scannedBarcode);
    if (product) {
      Alert.alert(
        '扫描结果',
        `商品: ${product.name}\n` +
        `当前库存: ${product.quantity}\n` +
        `存放位置: ${product.location}`,
        [
          { text: '取消', style: 'cancel' },
          { 
            text: '入库', 
            onPress: () => handleStockIn(product.id) 
          }
        ]
      );
    } else {
      Alert.alert('提示', '未找到对应商品,请检查条形码是否正确');
    }
  };

  const handleStockIn = (productId: string) => {
    Alert.prompt(
      '入库操作',
      '请输入入库数量:',
      [
        { text: '取消', style: 'cancel' },
        {
          text: '确定',
          onPress: (quantity) => {
            if (quantity && parseInt(quantity) > 0) {
              const product = products.find(p => p.id === productId);
              if (product) {
                // 分配库位
                const availableLocation = storageLocations.find(loc => 
                  loc.isAvailable && loc.currentStock + parseInt(quantity) <= loc.capacity
                );
                
                if (availableLocation) {
                  // 更新产品库存和位置
                  setProducts(products.map(p => 
                    p.id === productId 
                      ? { 
                          ...p, 
                          quantity: p.quantity + parseInt(quantity),
                          location: availableLocation.code
                        } 
                      : p
                  ));
                  
                  // 更新库位库存
                  // 这里简化处理,实际应该更新storageLocations状态
                  
                  // 添加库存记录
                  const record: InventoryRecord = {
                    id: (inventoryRecords.length + 1).toString(),
                    productId: productId,
                    operation: '入库',
                    quantity: parseInt(quantity),
                    timestamp: new Date().toLocaleString('zh-CN'),
                    operator: '当前用户'
                  };
                  setInventoryRecords([...inventoryRecords, record]);
                  
                  Alert.alert('成功', `商品已入库到${availableLocation.code}库位`);
                  setScannedBarcode('');
                  setNewQuantity('');
                } else {
                  Alert.alert('提示', '没有合适的库位,请检查库存容量');
                }
              }
            }
          }
        }
      ],
      'plain-text'
    );
  };

  const handleManualEntry = () => {
    if (!scannedBarcode.trim() || !newQuantity.trim()) {
      Alert.alert('提示', '请填写完整的商品信息');
      return;
    }

    const quantity = parseInt(newQuantity);
    if (isNaN(quantity) || quantity <= 0) {
      Alert.alert('提示', '请输入有效的数量');
      return;
    }

    const existingProduct = products.find(p => p.barcode === scannedBarcode);
    if (existingProduct) {
      // 更新现有商品
      setProducts(products.map(p => 
        p.id === existingProduct.id 
          ? { ...p, quantity: p.quantity + quantity } 
          : p
      ));
    } else {
      // 创建新商品
      const newProduct: Product = {
        id: (products.length + 1).toString(),
        name: `商品-${scannedBarcode.slice(-4)}`,
        barcode: scannedBarcode,
        quantity: quantity,
        location: '待分配',
        category: '未分类'
      };
      setProducts([...products, newProduct]);
    }

    // 添加库存记录
    const record: InventoryRecord = {
      id: (inventoryRecords.length + 1).toString(),
      productId: existingProduct?.id || (products.length + 1).toString(),
      operation: '入库',
      quantity: quantity,
      timestamp: new Date().toLocaleString('zh-CN'),
      operator: '当前用户'
    };
    setInventoryRecords([...inventoryRecords, record]);

    Alert.alert('成功', '库存已更新');
    setScannedBarcode('');
    setNewQuantity('');
  };

  return (
    <SafeAreaView style={styles.container}>
      {/* 头部 */}
      <View style={styles.header}>
        <Text style={styles.title}>仓库管理</Text>
        <Text style={styles.subtitle}>扫码入库,智能管理</Text>
      </View>

      <ScrollView style={styles.content}>
        {/* 条码扫描区域 */}
        <View style={styles.scanCard}>
          <Text style={styles.sectionTitle}>扫描入库</Text>
          
          <View style={styles.scanArea}>
            <Text style={styles.scanIcon}>📷</Text>
            <TextInput
              style={styles.barcodeInput}
              placeholder="扫描或输入条形码"
              value={scannedBarcode}
              onChangeText={setScannedBarcode}
            />
          </View>
          
          <TouchableOpacity 
            style={styles.scanButton}
            onPress={handleScanBarcode}
          >
            <Text style={styles.scanButtonText}>扫描条码</Text>
          </TouchableOpacity>
        </View>

        {/* 手动录入 */}
        <View style={styles.manualCard}>
          <Text style={styles.sectionTitle}>手动录入</Text>
          
          <TextInput
            style={styles.input}
            placeholder="入库数量"
            value={newQuantity}
            onChangeText={setNewQuantity}
            keyboardType="numeric"
          />
          
          <TouchableOpacity 
            style={styles.entryButton}
            onPress={handleManualEntry}
          >
            <Text style={styles.entryButtonText}>确认入库</Text>
          </TouchableOpacity>
        </View>

        {/* 库存列表 */}
        <View style={styles.inventoryCard}>
          <Text style={styles.sectionTitle}>当前库存</Text>
          
          {products.map(product => (
            <View key={product.id} style={styles.productItem}>
              <View style={styles.productInfo}>
                <Text style={styles.productName}>{product.name}</Text>
                <Text style={styles.productBarcode}>条码: {product.barcode}</Text>
                <Text style={styles.productLocation}>位置: {product.location}</Text>
              </View>
              <View style={styles.stockInfo}>
                <Text style={styles.stockQuantity}>{product.quantity}</Text>
                <Text style={styles.stockUnit}></Text>
              </View>
            </View>
          ))}
        </View>

        {/* 库位信息 */}
        <View style={styles.locationsCard}>
          <Text style={styles.sectionTitle}>库位状态</Text>
          
          {storageLocations.map(location => (
            <View key={location.id} style={styles.locationItem}>
              <View style={styles.locationInfo}>
                <Text style={styles.locationCode}>{location.code}</Text>
                <Text style={styles.locationCapacity}>
                  容量: {location.currentStock}/{location.capacity}
                </Text>
              </View>
              <View style={styles.locationStatus}>
                <View style={[
                  styles.statusIndicator,
                  { backgroundColor: location.isAvailable ? '#10b981' : '#6b7280' }
                ]} />
                <Text style={styles.statusText}>
                  {location.isAvailable ? '可用' : '已满'}
                </Text>
              </View>
            </View>
          ))}
        </View>

        {/* 操作记录 */}
        <View style={styles.recordsCard}>
          <Text style={styles.sectionTitle}>操作记录</Text>
          
          {inventoryRecords.slice(-5).reverse().map(record => {
            const product = products.find(p => p.id === record.productId);
            return (
              <View key={record.id} style={styles.recordItem}>
                <View style={styles.recordHeader}>
                  <Text style={styles.recordProduct}>{product?.name || '未知商品'}</Text>
                  <Text style={[
                    styles.recordOperation,
                    { color: record.operation === '入库' ? '#10b981' : '#ef4444' }
                  ]}>
                    {record.operation}
                  </Text>
                </View>
                <View style={styles.recordDetails}>
                  <Text style={styles.recordQuantity}>数量: {record.quantity}</Text>
                  <Text style={styles.recordTime}>{record.timestamp}</Text>
                </View>
              </View>
            );
          })}
        </View>

        {/* 使用说明 */}
        <View style={styles.infoCard}>
          <Text style={styles.sectionTitle}>使用说明</Text>
          <Text style={styles.infoText}>• 扫描商品条码快速入库</Text>
          <Text style={styles.infoText}>• 系统自动分配最优库位</Text>
          <Text style={styles.infoText}>• 实时更新库存数据</Text>
          <Text style={styles.infoText}>• 支持手动录入补充</Text>
        </View>
      </ScrollView>

      {/* 底部导航 */}
      <View style={styles.bottomNav}>
        <TouchableOpacity style={styles.navItem}>
          <Text style={styles.navIcon}>📱</Text>
          <Text style={styles.navText}>扫描</Text>
        </TouchableOpacity>
        <TouchableOpacity style={styles.navItem}>
          <Text style={styles.navIcon}>📦</Text>
          <Text style={styles.navText}>库存</Text>
        </TouchableOpacity>
        <TouchableOpacity style={styles.navItem}>
          <Text style={styles.navIcon}>📍</Text>
          <Text style={styles.navText}>库位</Text>
        </TouchableOpacity>
        <TouchableOpacity style={[styles.navItem, styles.activeNavItem]}>
          <Text style={styles.navIcon}>👤</Text>
          <Text style={styles.navText}>我的</Text>
        </TouchableOpacity>
      </View>
    </SafeAreaView>
  );
};

const styles = StyleSheet.create({
  container: {
    flex: 1,
    backgroundColor: '#f0f9ff',
  },
  header: {
    flexDirection: 'column',
    padding: 16,
    backgroundColor: '#ffffff',
    borderBottomWidth: 1,
    borderBottomColor: '#bae6fd',
  },
  title: {
    fontSize: 20,
    fontWeight: 'bold',
    color: '#0c4a6e',
    marginBottom: 4,
  },
  subtitle: {
    fontSize: 14,
    color: '#0284c7',
  },
  content: {
    flex: 1,
    marginTop: 12,
  },
  scanCard: {
    backgroundColor: '#ffffff',
    marginHorizontal: 16,
    marginBottom: 12,
    borderRadius: 12,
    padding: 16,
    elevation: 2,
    shadowColor: '#000',
    shadowOffset: { width: 0, height: 2 },
    shadowOpacity: 0.1,
    shadowRadius: 4,
  },
  sectionTitle: {
    fontSize: 16,
    fontWeight: '600',
    color: '#0c4a6e',
    marginBottom: 12,
  },
  scanArea: {
    flexDirection: 'row',
    alignItems: 'center',
    backgroundColor: '#f0f9ff',
    borderRadius: 8,
    paddingHorizontal: 12,
    marginBottom: 12,
  },
  scanIcon: {
    fontSize: 20,
    color: '#64748b',
    marginRight: 8,
  },
  barcodeInput: {
    flex: 1,
    paddingVertical: 12,
    fontSize: 14,
    color: '#0c4a6e',
  },
  scanButton: {
    backgroundColor: '#0284c7',
    paddingVertical: 14,
    borderRadius: 8,
    alignItems: 'center',
  },
  scanButtonText: {
    color: '#ffffff',
    fontSize: 16,
    fontWeight: '600',
  },
  manualCard: {
    backgroundColor: '#ffffff',
    marginHorizontal: 16,
    marginBottom: 12,
    borderRadius: 12,
    padding: 16,
    elevation: 2,
    shadowColor: '#000',
    shadowOffset: { width: 0, height: 2 },
    shadowOpacity: 0.1,
    shadowRadius: 4,
  },
  input: {
    borderWidth: 1,
    borderColor: '#bae6fd',
    borderRadius: 8,
    padding: 12,
    fontSize: 14,
    backgroundColor: '#f0f9ff',
    marginBottom: 12,
  },
  entryButton: {
    backgroundColor: '#10b981',
    paddingVertical: 14,
    borderRadius: 8,
    alignItems: 'center',
  },
  entryButtonText: {
    color: '#ffffff',
    fontSize: 16,
    fontWeight: '600',
  },
  inventoryCard: {
    backgroundColor: '#ffffff',
    marginHorizontal: 16,
    marginBottom: 12,
    borderRadius: 12,
    padding: 16,
    elevation: 2,
    shadowColor: '#000',
    shadowOffset: { width: 0, height: 2 },
    shadowOpacity: 0.1,
    shadowRadius: 4,
  },
  productItem: {
    flexDirection: 'row',
    justifyContent: 'space-between',
    alignItems: 'center',
    paddingVertical: 12,
    borderBottomWidth: 1,
    borderBottomColor: '#bae6fd',
  },
  productInfo: {
    flex: 1,
  },
  productName: {
    fontSize: 14,
    fontWeight: '500',
    color: '#0c4a6e',
    marginBottom: 2,
  },
  productBarcode: {
    fontSize: 12,
    color: '#64748b',
    marginBottom: 2,
  },
  productLocation: {
    fontSize: 12,
    color: '#64748b',
  },
  stockInfo: {
    alignItems: 'center',
  },
  stockQuantity: {
    fontSize: 18,
    fontWeight: 'bold',
    color: '#0284c7',
  },
  stockUnit: {
    fontSize: 12,
    color: '#64748b',
  },
  locationsCard: {
    backgroundColor: '#ffffff',
    marginHorizontal: 16,
    marginBottom: 12,
    borderRadius: 12,
    padding: 16,
    elevation: 2,
    shadowColor: '#000',
    shadowOffset: { width: 0, height: 2 },
    shadowOpacity: 0.1,
    shadowRadius: 4,
  },
  locationItem: {
    flexDirection: 'row',
    justifyContent: 'space-between',
    alignItems: 'center',
    paddingVertical: 12,
    borderBottomWidth: 1,
    borderBottomColor: '#bae6fd',
  },
  locationInfo: {
    flex: 1,
  },
  locationCode: {
    fontSize: 14,
    fontWeight: '500',
    color: '#0c4a6e',
    marginBottom: 2,
  },
  locationCapacity: {
    fontSize: 12,
    color: '#64748b',
  },
  locationStatus: {
    alignItems: 'center',
  },
  statusIndicator: {
    width: 12,
    height: 12,
    borderRadius: 6,
    marginBottom: 4,
  },
  recordsCard: {
    backgroundColor: '#ffffff',
    marginHorizontal: 16,
    marginBottom: 12,
    borderRadius: 12,
    padding: 16,
    elevation: 2,
    shadowColor: '#000',
    shadowOffset: { width: 0, height: 2 },
    shadowOpacity: 0.1,
    shadowRadius: 4,
  },
  recordItem: {
    paddingVertical: 12,
    borderBottomWidth: 1,
    borderBottomColor: '#bae6fd',
  },
  recordHeader: {
    flexDirection: 'row',
    justifyContent: 'space-between',
    alignItems: 'center',
    marginBottom: 4,
  },
  recordProduct: {
    fontSize: 14,
    fontWeight: '500',
    color: '#0c4a6e',
  },
  recordOperation: {
    fontSize: 12,
    fontWeight: '500',
  },
  recordDetails: {
    flexDirection: 'row',
    justifyContent: 'space-between',
  },
  recordQuantity: {
    fontSize: 12,
    color: '#64748b',
  },
  recordTime: {
    fontSize: 12,
    color: '#64748b',
  },
  infoCard: {
    backgroundColor: '#ffffff',
    marginHorizontal: 16,
    marginBottom: 80,
    borderRadius: 12,
    padding: 16,
    elevation: 2,
    shadowColor: '#000',
    shadowOffset: { width: 0, height: 2 },
    shadowOpacity: 0.1,
    shadowRadius: 4,
  },
  infoText: {
    fontSize: 14,
    color: '#64748b',
    lineHeight: 20,
    marginBottom: 4,
  },
  bottomNav: {
    flexDirection: 'row',
    justifyContent: 'space-around',
    backgroundColor: '#ffffff',
    borderTopWidth: 1,
    borderTopColor: '#bae6fd',
    paddingVertical: 12,
    position: 'absolute',
    bottom: 0,
    left: 0,
    right: 0,
  },
  navItem: {
    alignItems: 'center',
    flex: 1,
  },
  activeNavItem: {
    paddingTop: 4,
    borderTopWidth: 2,
    borderTopColor: '#0284c7',
  },
  navIcon: {
    fontSize: 20,
    color: '#94a3b8',
    marginBottom: 4,
  },
  activeNavIcon: {
    color: '#0284c7',
  },
  navText: {
    fontSize: 12,
    color: '#94a3b8',
  },
  activeNavText: {
    color: '#0284c7',
    fontWeight: '500',
  },
});

export default WarehouseManagementApp;


请添加图片描述


打包

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

在这里插入图片描述

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

在这里插入图片描述

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

请添加图片描述
本项目基于React Native开发跨平台仓库管理应用,支持iOS、Android和鸿蒙系统。采用函数式组件架构,通过TypeScript类型系统定义商品(Product)、库存记录(InventoryRecord)和库位(StorageLocation)等核心数据结构。使用Hooks管理状态,实现商品入库、条形码扫描、库存记录等核心功能。应用采用模块化设计,包含数据验证、用户交互反馈等优化措施,同时针对鸿蒙平台进行适配,确保跨平台兼容性。技术栈包括React Native、TypeScript和Flexbox布局,提供响应式UI和良好的开发体验。

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

Logo

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

更多推荐