基于华为云码道(CodeArts)从0到1开发HarmonyOS物品收纳助手应用实践

举报
GoodTimeGGB 发表于 2026/05/28 15:58:50 2026/05/28
【摘要】 基于HarmonyOS的物品收纳助手应用开发实践 一、项目概述 1.1 开发背景在日常生活中,我们经常面临物品收纳管理的困扰:找不到东西、忘记物品存放位置、重复购买已有物品等问题屡见不鲜。随着家庭物品的增多,如何高效地管理和追踪这些物品成为一个亟待解决的实际需求。基于这一痛点,我们开发了"物品收纳助手"应用,旨在帮助用户通过数字化方式管理家庭物品,实现物品信息的快速录入、便捷查询和智能分类...

基于HarmonyOS的物品收纳助手应用开发实践

一、项目概述

1.1 开发背景

在日常生活中,我们经常面临物品收纳管理的困扰:找不到东西、忘记物品存放位置、重复购买已有物品等问题屡见不鲜。随着家庭物品的增多,如何高效地管理和追踪这些物品成为一个亟待解决的实际需求。

基于这一痛点,我们开发了"物品收纳助手"应用,旨在帮助用户通过数字化方式管理家庭物品,实现物品信息的快速录入、便捷查询和智能分类,让收纳管理变得简单高效。

1.2 应用简介

物品收纳助手是一款基于HarmonyOS原生开发的移动应用,采用ArkTS语言和ArkUI框架构建,为用户提供完整的物品管理解决方案。

核心功能:

  • 📸 拍照录入:支持拍照或从相册选择图片,为物品添加视觉记录
  • 🏷️ 智能分类:预设多种常用分类,支持自定义分类和标签管理
  • 📍 位置管理:多层级位置体系,精确定位物品存放位置
  • 🔍 快速搜索:支持关键词搜索、多条件筛选、排序功能
  • 收藏功能:标记重要物品,快速访问常用物品
  • 📊 数据统计:直观展示物品数量、分类分布等统计信息
  • 💾 数据备份:支持数据导出备份和恢复,保障数据安全

技术规格:

  • 开发语言:ArkTS
  • UI框架:ArkUI
  • 目标平台:HarmonyOS 6.0.0 (API 22)
  • 构建工具:Hvigor
  • 数据存储:关系型数据库 (RDB)

1.3 应用界面展示

首页界面

首页展示搜索栏、物品统计卡片、分类网格和快捷操作入口,底部为四个Tab导航(首页/添加/列表/设置)。
1.png

物品列表与搜索

3.png

物品列表支持关键词搜索、多条件筛选和排序,可通过分类、位置、收藏等维度快速定位物品。

添加物品

2.png

添加物品页面支持拍照或从相册选择图片,填写物品名称、描述、分类、位置、购买日期、价格等信息。

二、技术架构设计

2.1 整体架构

应用采用分层架构设计,将系统划分为表现层、业务逻辑层和数据访问层,各层职责清晰,便于维护和扩展。

┌─────────────────────────────────────────────────────────┐
│                    表现层 (UI Layer)                      │
│  ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐       │
│  │  Pages  │ │Components│ │ Models  │ │ Config  │       │
│  └─────────┘ └─────────┘ └─────────┘ └─────────┘       │
├─────────────────────────────────────────────────────────┤
│                  业务逻辑层 (Service Layer)               │
│  ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐   │
│  │ItemService│ │CategorySvc│ │LocationSvc│ │BackupSvc│   │
│  └──────────┘ └──────────┘ └──────────┘ └──────────┘   │
├─────────────────────────────────────────────────────────┤
│                  数据访问层 (Data Layer)                   │
│  ┌────────────────┐ ┌────────────────┐                  │
│  │ DatabaseManager │ │  PhotoService  │                  │
│  └────────────────┘ └────────────────┘                  │
└─────────────────────────────────────────────────────────┘

2.2 目录结构

entry/src/main/ets/
├── pages/                    # 页面
│   ├── Index.ets            # 首页
│   ├── AddItem.ets          # 添加物品页
│   ├── ItemDetail.ets       # 物品详情页
│   ├── ItemList.ets         # 物品列表页
│   └── Settings.ets         # 设置页
├── components/              # 可复用组件
│   ├── SearchBar.ets        # 搜索栏
│   ├── StatisticsCard.ets   # 统计卡片
│   ├── CategoryGrid.ets     # 分类网格
│   ├── PhotoPicker.ets      # 照片选择器
│   ├── ItemForm.ets         # 物品表单
│   └── FilterPanel.ets      # 筛选面板
├── services/                # 业务服务
│   ├── DatabaseManager.ets  # 数据库管理
│   ├── ItemService.ets      # 物品服务
│   ├── CategoryService.ets  # 分类服务
│   ├── LocationService.ets  # 位置服务
│   ├── BackupService.ets    # 备份服务
│   └── PhotoService.ets     # 照片服务
├── models/                  # 数据模型
│   ├── Item.ets             # 物品模型
│   ├── Category.ets         # 分类模型
│   ├── Location.ets         # 位置模型
│   └── Tag.ets              # 标签模型
├── utils/                   # 工具类
└── config/                  # 配置

三、核心技术实现

3.1 数据库设计

应用使用HarmonyOS提供的关系型数据库(RDB)进行数据持久化,设计了完善的数据库表结构。

数据库表设计:

// 分类表
CREATE TABLE IF NOT EXISTS categories (
  id INTEGER PRIMARY KEY AUTOINCREMENT,
  name TEXT NOT NULL UNIQUE,
  color TEXT NOT NULL DEFAULT '#4CAF50',
  icon TEXT,
  is_default INTEGER NOT NULL DEFAULT 0,
  description TEXT,
  created_at TEXT NOT NULL DEFAULT (datetime('now', 'localtime')),
  updated_at TEXT NOT NULL DEFAULT (datetime('now', 'localtime'))
)

// 物品表
CREATE TABLE IF NOT EXISTS items (
  id INTEGER PRIMARY KEY AUTOINCREMENT,
  name TEXT NOT NULL,
  description TEXT,
  quantity INTEGER NOT NULL DEFAULT 1,
  category_id INTEGER,
  location_id INTEGER,
  purchase_date TEXT,
  expiry_date TEXT,
  price REAL,
  notes TEXT,
  is_favorite INTEGER NOT NULL DEFAULT 0,
  created_at TEXT NOT NULL DEFAULT (datetime('now', 'localtime')),
  updated_at TEXT NOT NULL DEFAULT (datetime('now', 'localtime')),
  FOREIGN KEY (category_id) REFERENCES categories (id) ON DELETE SET NULL,
  FOREIGN KEY (location_id) REFERENCES locations (id) ON DELETE SET NULL
)

// 位置表(支持多层级)
CREATE TABLE IF NOT EXISTS locations (
  id INTEGER PRIMARY KEY AUTOINCREMENT,
  name TEXT NOT NULL,
  parent_id INTEGER,
  level INTEGER NOT NULL DEFAULT 1,
  description TEXT,
  created_at TEXT NOT NULL DEFAULT (datetime('now', 'localtime')),
  updated_at TEXT NOT NULL DEFAULT (datetime('now', 'localtime')),
  FOREIGN KEY (parent_id) REFERENCES locations (id) ON DELETE SET NULL,
  UNIQUE(name, parent_id, level)
)

数据库管理器实现:

export class DatabaseManager {
  private static instance: DatabaseManager;
  private store: relationalStore.RdbStore | null = null;
  private readonly DB_NAME = 'ItemStorageAssistant.db';

  static getInstance(): DatabaseManager {
    if (!DatabaseManager.instance) {
      DatabaseManager.instance = new DatabaseManager();
    }
    return DatabaseManager.instance;
  }

  async initialize(context: Context): Promise<boolean> {
    const config: relationalStore.StoreConfig = {
      name: this.DB_NAME,
      securityLevel: relationalStore.SecurityLevel.S1,
      encrypt: false
    };
    this.store = await relationalStore.getRdbStore(context, config);
    await this.createTables();
    return true;
  }
}

3.2 ArkUI组件化开发

应用充分利用ArkUI的声明式UI特性,采用组件化开发模式,提高代码复用性和可维护性。

统计卡片组件示例:

@Component
export struct StatisticsCard {
  @Prop statistics: Statistics = {
    totalItems: 0,
    totalCategories: 0,
    totalLocations: 0,
    totalPhotos: 0,
    favoriteItems: 0,
    recentItems: 0
  };

  build() {
    Column() {
      Text('物品统计')
        .fontSize(16)
        .fontWeight(FontWeight.Bold)
        .fontColor($r('app.color.text_primary'))
        .margin({ bottom: 12 })

      Row() {
        this.StatItem('总物品', this.statistics.totalItems.toString())
        this.StatItem('分类数', this.statistics.totalCategories.toString())
        this.StatItem('位置数', this.statistics.totalLocations.toString())
      }
      .width('100%')
      .justifyContent(FlexAlign.SpaceBetween)
    }
    .width('100%')
    .backgroundColor($r('app.color.white'))
    .borderRadius(12)
    .padding(16)
    .shadow({
      radius: 4,
      color: '#1A000000',
      offsetX: 0,
      offsetY: 2
    })
  }

  @Builder StatItem(label: string, value: string) {
    Column() {
      Text(value)
        .fontSize(20)
        .fontWeight(FontWeight.Bold)
        .fontColor($r('app.color.primary_green'))
      
      Text(label)
        .fontSize(12)
        .fontColor($r('app.color.text_gray'))
        .margin({ top: 4 })
    }
    .alignItems(HorizontalAlign.Center)
  }
}

照片选择器组件:

@Component
export struct PhotoPicker {
  @State selectedPhotos: string[] = [];
  @Prop options: PhotoPickerOptions = {
    maxCount: 5,
    allowCamera: true,
    allowGallery: true
  };
  onPhotosSelected?: (photos: string[]) => void;

  private async openCamera() {
    const photoSelectOptions = new picker.PhotoSelectOptions();
    photoSelectOptions.MIMEType = picker.PhotoViewMIMETypes.IMAGE_TYPE;
    photoSelectOptions.maxSelectNumber = this.options.maxCount - this.selectedPhotos.length;

    const photoPicker = new picker.PhotoViewPicker();
    const photoSelectResult = await photoPicker.select(photoSelectOptions);

    if (photoSelectResult && photoSelectResult.photoUris) {
      photoSelectResult.photoUris.forEach(uri => {
        this.addPhoto(uri);
      });
    }
  }

  build() {
    Column() {
      // 照片网格展示
      Grid() {
        ForEach(this.selectedPhotos, (photo: string, index: number) => {
          GridItem() {
            this.PhotoItem(photo, index)
          }
        })
      }
      .columnsTemplate('1fr 1fr 1fr')
      
      // 操作按钮
      Row() {
        Button('拍照')
          .onClick(() => { this.openCamera(); })
        Button('从相册选择')
          .onClick(() => { this.openGallery(); })
      }
    }
  }
}

3.3 数据备份与恢复

为了保障用户数据安全,应用实现了完整的数据备份和恢复功能。

export class BackupService {
  async createBackup(options: BackupOptions): Promise<string> {
    // 收集所有数据
    const items = await this.itemService.getAllItems();
    const categories = await this.categoryService.getAllCategories();
    const locations = await this.locationService.getAllLocations();
    const tags = await this.tagService.getAllTags();

    // 构建备份数据结构
    const backupData: BackupData = {
      version: '1.0.0',
      timestamp: new Date().toISOString(),
      items: items,
      categories: categories,
      locations: locations,
      tags: tags
    };

    // 写入文件
    const fileName = `backup_${timestamp}.json`;
    const filePath = `${this.context.filesDir}/backups/${fileName}`;
    
    const backupJson = JSON.stringify(backupData, null, 2);
    const encoder = new util.TextEncoder();
    const uint8Array = encoder.encodeInto(backupJson);
    const file = fs.openSync(filePath, fs.OpenMode.CREATE | fs.OpenMode.WRITE_ONLY);
    fs.writeSync(file.fd, uint8Array);
    fs.closeSync(file);

    return filePath;
  }

  async restoreBackup(filePath: string): Promise<void> {
    // 读取备份文件
    const file = fs.openSync(filePath, fs.OpenMode.READ_ONLY);
    const buffer = new ArrayBuffer(fs.statSync(filePath).size);
    fs.readSync(file.fd, buffer);
    fs.closeSync(file);

    // 解析并恢复数据
    const decoder = new util.TextDecoder('utf-8');
    const backupJson = decoder.decodeWithStream(new Uint8Array(buffer));
    const backupData = JSON.parse(backupJson) as BackupData;

    // 按依赖顺序恢复数据
    await this.restoreData(backupData);
  }
}

3.4 页面导航与状态管理

应用使用HarmonyOS的路由机制实现页面导航,结合@State和@Prop装饰器管理组件状态。

首页实现:

@Entry
@Component
struct Index {
  @State currentTabIndex: number = TabIndex.HOME;
  @State statistics: Statistics = {
    totalItems: 0,
    totalCategories: 0,
    totalLocations: 0,
    totalPhotos: 0,
    favoriteItems: 0,
    recentItems: 0
  };
  @State categories: Category[] = [];
  @State isLoading: boolean = true;

  async aboutToAppear() {
    await this.initializeData();
  }

  onSearch = (searchText: string) => {
    if (searchText.trim()) {
      router.pushUrl({
        url: 'pages/ItemList',
        params: { searchText: searchText }
      });
    }
  }

  onCategoryClick = (category: Category) => {
    router.pushUrl({
      url: 'pages/ItemList',
      params: { categoryId: category.id }
    });
  }

  build() {
    Column() {
      SearchBar({
        onSearch: this.onSearch,
        placeholder: '搜索物品名称、描述...'
      })

      Scroll() {
        Column() {
          StatisticsCard({ statistics: this.statistics })
          CategoryGrid({
            categories: this.categories,
            onCategoryClick: this.onCategoryClick
          })
        }
      }

      BottomNavigation({
        currentIndex: $currentTabIndex,
        onTabChange: this.onTabChange
      })
    }
    .width('100%')
    .height('100%')
    .backgroundColor($r('app.color.background_gray'))
  }
}

四、开发过程中的挑战与解决方案

4.1 数据库索引优化

挑战: 随着物品数量增加,搜索和筛选操作的性能下降明显。

解决方案: 为常用查询字段创建索引,优化查询效率。

private async createIndexes(): Promise<void> {
  const indexes = [
    'CREATE INDEX IF NOT EXISTS idx_items_name ON items (name)',
    'CREATE INDEX IF NOT EXISTS idx_items_category ON items (category_id)',
    'CREATE INDEX IF NOT EXISTS idx_items_location ON items (location_id)',
    'CREATE INDEX IF NOT EXISTS idx_items_favorite ON items (is_favorite)',
    'CREATE INDEX IF NOT EXISTS idx_items_created_at ON items (created_at)',
    'CREATE INDEX IF NOT EXISTS idx_locations_parent ON locations (parent_id)',
    'CREATE INDEX IF NOT EXISTS idx_item_tags_item ON item_tags (item_id)',
    'CREATE INDEX IF NOT EXISTS idx_photos_item ON photos (item_id)'
  ];

  for (const sql of indexes) {
    await this.store.executeSql(sql);
  }
}

4.2 多层级位置管理

挑战: 物品存放位置往往具有层级关系(如:客厅-电视柜-第一层),需要支持多层级管理。

解决方案: 设计自引用的位置表结构,通过parent_id和level字段实现层级关系。

export interface Location {
  id: number;
  name: string;
  parentId?: number;      // 父位置ID
  level: number;          // 层级深度
  description?: string;
  createdAt: Date;
  updatedAt: Date;
}

// 获取完整位置路径
async getLocationPath(locationId: number): Promise<string> {
  const path: string[] = [];
  let currentId: number | undefined = locationId;
  
  while (currentId) {
    const location = await this.getLocationById(currentId);
    if (location) {
      path.unshift(location.name);
      currentId = location.parentId;
    } else {
      break;
    }
  }
  
  return path.join(' > ');
}

4.3 照片存储管理

挑战: 照片文件占用存储空间大,需要合理管理文件存储路径和清理机制。

解决方案:

  • 将照片存储在应用专属目录下
  • 实现临时文件自动清理机制
  • 备份时可选是否包含照片
async cleanupTempFiles(): Promise<void> {
  const tempDir = `${this.context.cacheDir}`;
  
  if (fs.accessSync(tempDir)) {
    const files = fs.listFileSync(tempDir);
    
    for (const fileName of files) {
      const filePath = `${tempDir}/${fileName}`;
      const stat = fs.statSync(filePath);
      
      // 清理超过7天的临时文件
      const daysDiff = (Date.now() - stat.mtime) / (1000 * 60 * 60 * 24);
      if (daysDiff > 7) {
        fs.unlinkSync(filePath);
      }
    }
  }
}

4.4 图片显示黑屏问题

挑战: 新增物品添加图片后,查看物品信息时图片不显示,查看图片详情显示黑屏。

原因分析: HarmonyOS的Image组件加载本地沙箱文件时,需要使用file://协议前缀。直接使用文件路径会导致图片无法正确解析,显示为黑屏。

解决方案: 为图片路径添加file://前缀,并增加图片加载错误处理回调。

// 修改前 - 直接使用文件路径,导致黑屏
Image(photo.filePath)

// 修改后 - 添加file://协议前缀,并增加错误处理
Image('file://' + photo.filePath)
  .onError(() => {
    console.error('图片加载失败:', photo.filePath);
  })

经验总结: 在HarmonyOS中加载应用沙箱目录下的图片文件,必须使用file://协议前缀,这是与Android平台的一个重要差异。同时,为Image组件添加onError回调有助于快速定位图片加载问题。

4.5 日期选择时区偏移问题

挑战: 用户选择购买日期后保存,查看物品信息时日期显示为选择日期的前一天。更严重的是,DatePickerDialog的month参数处理也存在偏移,导致月份显示错误(如选择3月显示为2月)。

原因分析: JavaScript/TypeScript中Date构造函数的月份参数从0开始(0=一月),而HarmonyOS的DatePickerDialog回调的value.month已经是从1开始的值。此外,创建Date对象时默认使用UTC零点时间,在东八区(UTC+8)时区下,UTC零点对应北京时间早上8点,而如果日期被解析为本地时区的零点,则UTC时间为前一天16点,从而导致日期回退一天。

解决方案:

  1. 正确处理DatePickerDialog的month参数,不再额外减1
  2. 创建日期时设置时间为中午12点,避免时区转换导致的日期偏移
// 问题代码1:month多减了1
const picked = new Date(value.year, value.month - 1, value.day);

// 修复后:DatePickerDialog的month已从1开始,无需减1
const picked = new Date(value.year, value.month, value.day, 12, 0, 0);

经验总结: 处理日期时间时需特别注意:①不同平台/组件对月份的起始值定义不同;②创建Date对象时应考虑时区影响,设置正午时间可避免跨日偏移。

4.6 深色模式适配问题

挑战: 应用在设备切换为深色模式后,首页搜索框输入内容看不见,添加物品页面输入框输入内容不清晰,整体UI无法正常适配深色模式。

原因分析: 应用只定义了base目录下的颜色资源,缺少dark目录下的对应颜色配置。HarmonyOS的资源系统在深色模式下会查找dark/element/color.json,如果找不到则回退使用base资源,导致深色背景下显示深色文字,内容不可见。

解决方案: 完善entry/src/main/resources/dark/element/color.json,为所有颜色资源添加深色模式对应的值。

{
  "color": [
    { "name": "primary_green", "value": "#66BB6A" },
    { "name": "background_gray", "value": "#1E1E1E" },
    { "name": "background_light", "value": "#121212" },
    { "name": "text_primary", "value": "#FFFFFF" },
    { "name": "text_gray", "value": "#9E9E9E" },
    { "name": "white", "value": "#121212" },
    { "name": "primary_light", "value": "#1B3D1B" }
  ]
}

经验总结: HarmonyOS应用开发中,深色模式适配是必做项。需在resources/dark/目录下为所有颜色资源提供深色版本,关键原则是:深色模式下背景色变深、文字色变浅、主题色适当提亮。

4.7 操作弹窗点击外部报错

挑战: 物品详情页面的"更多操作"弹窗,点击弹窗外部区域时,页面提示"操作菜单打开失败"错误。

原因分析: 使用了promptAction.showDialog实现操作菜单,该API在用户点击外部关闭时会产生错误回调,并非真正的操作菜单行为。

解决方案:showDialog替换为showActionMenu,后者在点击外部时会自动关闭而不报错。

// 修改前:showDialog点击外部报错
promptAction.showDialog({
  title: '更多操作',
  message: '请选择操作',
  buttons: [...]
}, ...)

// 修改后:showActionMenu点击外部自动关闭
promptAction.showActionMenu({
  title: '更多操作',
  buttons: [
    { text: '编辑', color: '#4CAF50' },
    { text: '删除', color: '#F44336' },
    { text: '取消', color: '#666666' }
  ]
}, ...)

经验总结: showDialog用于确认/提示类弹窗,showActionMenu用于操作选择类菜单,两者语义不同,需根据场景选择合适的API。

4.8 备份功能时间戳错误

挑战: 备份文件显示的时间为1970年,与实际时间严重不符。

原因分析: fs.statSync()返回的mtime属性类型为number(毫秒时间戳),而非Date对象。直接使用new Date(stat.mtime)构造时,由于TypeScript类型推断问题,可能导致类型转换异常。

解决方案: 显式将mtime转换为number类型再创建Date对象。

// 修改前:类型转换错误导致1970年
const mtime = new Date(stat.mtime);

// 修改后:显式类型转换
const mtime = new Date(stat.mtime as number);

经验总结: HarmonyOS文件系统API返回的类型可能与TypeScript类型声明不完全匹配,涉及类型转换时应添加显式类型断言,避免隐式转换导致的运行时错误。

4.9 相册访问权限策略

挑战: 应用需要访问用户相册以选择物品照片,但直接申请ohos.permission.READ_IMAGEVIDEOohos.permission.WRITE_IMAGEVIDEO权限会被应用市场审核拒绝,因为这两个权限为user_grant级别,用户体验差且审核严格。

解决方案: 使用HarmonyOS提供的PhotoViewPicker系统组件访问相册,该组件由系统UI代理执行,无需应用自行申请读写权限。

const photoSelectOptions = new picker.PhotoSelectOptions();
photoSelectOptions.MIMEType = picker.PhotoViewMIMETypes.IMAGE_TYPE;
photoSelectOptions.maxSelectNumber = maxCount;

const photoPicker = new picker.PhotoViewPicker();
const photoSelectResult = await photoPicker.select(photoSelectOptions);

如需保存到图库: 使用系统提供的SaveButton安全控件或showAssetsCreationDialog方法,同样无需申请写入权限。

经验总结: HarmonyOS的权限管控日趋严格,应优先使用系统安全组件(PhotoViewPicker、SaveButton等)替代直接权限申请,既保证功能完整,又能顺利通过应用审核。

五、应用签名与发布

5.1 签名配置

应用发布前需要进行签名配置,确保应用的安全性和完整性。

{
  "app": {
    "signingConfigs": [
      {
        "name": "default",
        "type": "HarmonyOS",
        "material": {
          "storeFile": "D:/PersonalProject/isa.p12",
          "keyAlias": "isa",
          "signAlg": "SHA256withECDSA",
          "profile": "D:/PersonalProject/isaRelease.p7b",
          "certpath": "D:/PersonalProject/isa.cer"
        }
      }
    ]
  }
}

5.2 构建发布包

使用DevEco Studio构建签名的应用包:

  1. 打开项目,等待同步完成
  2. 点击 BuildBuild Hap(s)/APP(s)Build APP(s)
  3. 构建完成后,在 build/outputs/default/ 目录获取签名的.app文件

发布包信息:

  • 文件名:ItemStorageAssistant_v1.0.0_signed.app
  • 文件大小:约4.5MB
  • 目标设备:手机

5.3 发布到华为应用市场

应用签名构建完成后,需通过AppGallery Connect提交到华为应用市场。

提交流程:

  1. 登录AppGallery Connect:访问华为开发者联盟,使用开发者账号登录
  2. 创建应用:填写应用名称(物品收纳助手)、包名(com.goodtime.itemstorageassistant)、分类(效率办公 > 工具)
  3. 上传应用包:上传已签名的.app文件(注意:必须使用.app格式,.hap格式仅用于开发测试)
  4. 填写应用信息:应用描述、关键词、隐私政策等
  5. 上传资源:应用图标(512x512px PNG)、应用截图(至少3张,建议5张)
  6. 提交审核:确认所有信息填写完成后提交

应用截图要求: 截图内容应为真实功能展示,不能包含测试数据,建议截取以下5个页面:

  1. 首页界面 - 展示统计卡片和分类网格
  2. 物品列表 - 展示搜索筛选功能
  3. 添加物品 - 展示表单和拍照功能
  4. 物品详情 - 展示信息查看和操作
  5. 设置页面 - 展示管理功能入口

5.4 应用审核常见问题与解决

在提交华为应用市场审核过程中,我们遇到了以下典型问题:

问题1:应用图标透明背景

审核意见: 包体内配置的图标存在透明背景的问题,影响整体展示效果和用户体验。

解决方案:background.png设置纯色背景,使用应用主题色(#4CAF50)替代透明背景。HarmonyOS应用图标采用分层设计(foreground + background),background层必须为不透明的纯色,否则在应用市场展示时会出现异常。

问题2:截图包含测试数据

审核意见: 应用截图含有测试数据,影响用户体验。

解决方案: 清空应用中的测试数据,使用真实场景数据重新截取应用截图。审核要求截图中展示的内容应为真实、合理的用户数据,不能出现"测试"、"test"等明显测试标记。

问题3:深色模式未适配

审核意见: 应用在深色模式下未正常适配,部分内容不可见。

解决方案: 完善resources/dark/目录下的颜色资源配置(详见4.6节)。华为应用市场对深色模式适配有硬性要求,未适配深色模式的应用将被拒绝。

问题4:功能不完整但可操作

审核意见: 备份功能未完成但按钮可点击,用户点击后显示失败。

解决方案: 为未完成的功能添加"开发中"标签,禁用对应按钮并添加提示文字,避免用户产生功能异常的印象。

// 为未完成功能添加标识
Button('创建备份')
  .enabled(false)  // 禁用按钮
  .backgroundColor('#FF9800')  // 橙色标识

Text('功能开发中,敬请期待')
  .fontSize(12)
  .fontColor('#FF9800')

审核经验总结:

审核要点 常见问题 预防措施
图标规范 透明背景、尺寸不符 使用纯色背景,严格按规范提供各尺寸
截图质量 包含测试数据、截图模糊 使用真实数据,高分辨率截图
深色模式 未适配、内容不可见 提交前在深色模式下逐页检查
功能完整性 未完成功能可操作 禁用或隐藏未完成功能
权限合理性 申请不必要的权限 优先使用系统安全组件

六、开发心得与总结

6.1 HarmonyOS开发体验

通过本次开发实践,我们深刻体会到HarmonyOS开发的优势:

  1. ArkTS语言:基于TypeScript扩展,语法简洁,类型安全,降低了学习成本
  2. ArkUI框架:声明式UI开发模式,代码简洁直观,开发效率高
  3. 丰富的API:HarmonyOS提供了完善的系统能力API,如文件管理、数据库、相机等
  4. DevEco Studio:专业的IDE工具,提供代码智能提示、调试、性能分析等功能

同时也遇到了一些需要注意的差异化特性:

  1. 文件路径协议:加载沙箱文件必须使用file://前缀,与Android习惯不同
  2. 日期处理差异:DatePickerDialog的月份从1开始,与JS的Date构造函数(从0开始)不一致
  3. 类型系统:部分系统API返回类型与TypeScript声明不完全匹配,需要显式类型断言
  4. 权限模型:优先使用系统安全组件替代直接权限申请,审核更易通过
  5. 深色模式:必须提供dark目录下的资源,否则审核可能被拒

6.2 最佳实践总结

  1. 架构设计:采用分层架构,职责清晰,便于维护和测试
  2. 组件化开发:将UI拆分为可复用组件,提高开发效率
  3. 数据模型设计:合理设计数据库表结构和索引,保证查询性能
  4. 错误处理:统一的错误处理机制,提升应用稳定性
  5. 用户体验:注重交互细节,提供流畅的操作体验

6.3 未来展望

后续计划为应用增加更多功能:

  • 🔔 到期提醒:物品保质期/保修期到期提醒
  • 📤 数据同步:支持云端数据同步,多设备数据共享
  • 🤖 智能识别:集成AI能力,实现物品图片自动识别分类
  • 📊 数据分析:更丰富的数据统计和可视化展示
  • 🌐 多语言支持:支持中英文等多语言切换

七、结语

"物品收纳助手"应用的开发实践,展示了如何使用HarmonyOS原生技术栈构建一个完整的移动应用。从需求分析、架构设计、功能实现到应用发布,我们完整体验了HarmonyOS应用开发的全流程。

HarmonyOS为开发者提供了强大的技术支持和完善的开发工具,使得我们能够专注于业务逻辑的实现,快速构建高质量的应用。相信随着HarmonyOS生态的不断发展,将会有更多优秀的应用涌现,为用户带来更好的使用体验。


应用信息:

  • 应用名称:物品收纳助手
  • 包名:com.goodtime.itemstorageassistant
  • 版本:1.0.0
  • 开发者:goodtime
  • 目标平台:HarmonyOS 6.0.0

技术栈:

  • 开发语言:ArkTS
  • UI框架:ArkUI
  • 数据存储:关系型数据库 (RDB)
  • 构建工具:Hvigor
  • 开发工具:DevEco Studio
【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。