基于 HarmonyOS 5.0 的美食抽签应用开发实践

基于 HarmonyOS 5.0 的美食抽签应用开发实践

前言

本文记录了一个基于 HarmonyOS 5.0 开发的美食抽签应用实践经验。这是一个解决"吃什么"日常困扰的应用,具有以下特点:

  1. 核心功能

    • 支持早中晚餐分类管理
    • 提供随机抽签选择功能
    • 记录历史抽签数据
    • 展示数据统计分析
  2. 交互设计

    • 流畅的抽签动画效果
    • 精美的食物图标展示
    • 符合直觉的手势操作
    • 清晰的数据可视化
  3. 技术特点

    • 采用 Stage 模型的单 Ability 架构
    • 使用 ArkTS 声明式 UI 开发
    • 基于首选项的数据持久化
    • 支持深浅色主题切换
  4. 项目规范

    • 遵循 TypeScript 开发规范
    • 采用组件化开发方案
    • 统一的错误处理机制
    • 规范的代码组织结构

功能模块

1. 抽签模块

  • 支持按餐点类型抽签
  • 提供抽签动画效果
  • 记录抽签历史
  • 展示抽签结果

2. 管理模块

  • 食物分类管理
  • 自定义添加食物
  • 食物图标选择
  • 批量导入导出

3. 统计模块

  • 展示抽签频率统计
  • 提供历史记录查询
  • 数据可视化图表
  • 使用习惯分析

开发环境

  • HarmonyOS SDK: 5.0.0
  • DevEco Studio: 5.0
  • Node.js: 18.12.0
  • ohpm: 1.0.0

技术栈

  • 开发框架:ArkTS + HarmonyOS 5.0
  • UI 框架:ArkUI 5.0
  • 状态管理:@State、@Link、@Prop、@Provide/@Consume
  • 数据持久化:Data Preferences
  • 动画系统:ArkTS Animation API

项目结构

entry/src/main/ets/
├── pages/                 # 页面目录
│   ├── Index.ets         # 主页面
│   └── tabs/             # 标签页
│       ├── DrawPage.ets  # 抽签页面
│       ├── ManagePage.ets# 管理页面
│       └── StatsPage.ets # 统计页面
├── services/             # 服务层
│   ├── FoodListService.ets  # 食物列表服务
│   └── FoodDataService.ets  # 数据服务
├── components/           # 组件目录
│   ├── FoodCard.ets     # 食物卡片组件
│   └── StatChart.ets    # 统计图表组件
├── common/              # 公共目录
│   ├── constants/       # 常量定义
│   └── utils/          # 工具函数
└── models/             # 数据模型
    └── FoodModel.ets   # 食物数据模型

数据结构设计

// 食物数据模型
interface FoodItem {
    id: string // 唯一标识
    name: string // 食物名称
    type: MealType // 餐点类型
    icon?: string // 食物图标
    createTime: number // 创建时间
}

// 餐点类型枚举
enum MealType {
    BREAKFAST = 'breakfast',
    LUNCH = 'lunch',
    DINNER = 'dinner',
}

// 抽签记录
interface DrawRecord {
    id: string // 记录ID
    foodId: string // 食物ID
    time: number // 抽签时间
    type: MealType // 餐点类型
}

// 统计数据
interface FoodStat {
    foodId: string // 食物ID
    count: number // 抽签次数
    lastTime: number // 最后抽签时间
}

核心功能实现

1. Stage 模型应用

import AbilityConstant from '@ohos.app.ability.AbilityConstant'
import UIAbility from '@ohos.app.ability.UIAbility'
import window from '@ohos.window'

export default class EntryAbility extends UIAbility {
    onCreate(want: Want) {
        // 应用初始化
    }

    onWindowStageCreate(windowStage: window.WindowStage) {
        // 加载主页面
        windowStage.loadContent('pages/Index', (err, data) => {
            if (err.code) {
                console.error('Failed to load content', err.code)
                return
            }
        })
    }
}

2. 数据服务实现

export class FoodDataService {
    private static instance: FoodDataService
    private preferences: data_preferences.Preferences | null = null
    private readonly STORE_NAME: string = 'FoodPreferences'

    public static getInstance(): FoodDataService {
        if (!FoodDataService.instance) {
            FoodDataService.instance = new FoodDataService()
        }
        return FoodDataService.instance
    }

    public async getFoodsByType(type: MealType): Promise<FoodItem[]> {
        const foods = await this.getAllFoods()
        return foods.filter((item) => item.type === type)
    }

    private async ensureInitialized(): Promise<void> {
        if (!this.preferences) {
            await this.init()
        }
    }
}

3. 自定义组件封装

@Component
struct FoodCard {
  @Prop food: FoodItem
  @State private isPressed: boolean = false
  @Consume('themeData') theme: Theme

  build() {
    Row() {
      Text(this.food.icon)
        .fontSize(24)
      Text(this.food.name)
        .fontSize(16)
        .fontColor(this.theme.textColor)
    }
    .width('100%')
    .padding(16)
    .backgroundColor(this.isPressed ?
      this.theme.pressedColor :
      this.theme.backgroundColor)
    .borderRadius(8)
    .stateStyles({
      pressed: {
        transform: { scale: 0.98 },
        opacity: 0.8
      }
    })
  }
}

4. 性能优化实践

  1. 列表性能优化:
@Component
struct OptimizedList {
  @State private foodList: FoodItem[] = []

  build() {
    List({ space: 12 }) {
      LazyForEach(this.foodList,
        (food: FoodItem) => this.ListItem(food),
        (food: FoodItem) => food.id
      )
    }
    .onScrollIndex((first: number, last: number) => {
      this.handleVisibleItemsChange(first, last)
    })
  }

  @Builder
  private ListItem(food: FoodItem) {
    Row() {
      Text(food.icon)
        .fontSize(24)
      Text(food.name)
        .fontSize(16)
    }
    .width('100%')
    .padding(16)
  }
}
  1. 启动优化:
aboutToAppear() {
  // 延迟加载非关键资源
  setTimeout(() => {
    this.loadNonCriticalResources()
  }, 0)

  // 并行加载数据
  Promise.all([
    this.loadFoodList(),
    this.loadUserPreferences(),
    this.loadThemeSettings()
  ]).catch(err => {
    console.error('Failed to load initial data:', err)
  })
}

5. 错误处理

class ErrorHandler {
    static handle(error: Error, context: string) {
        Logger.error(`[${context}]`, error)

        if (error instanceof NetworkError) {
            this.showToast('网络连接失败,请检查网络设置')
        } else if (error instanceof DataError) {
            this.showToast('数据加载失败,请稍后重试')
        }
    }

    private static showToast(message: string) {
        promptAction.showToast({
            message: message,
            duration: 3000,
            bottom: '100vp',
        })
    }
}

6. 主题系统实现

// 主题配置
interface ThemeConfig {
  colorMode: 'light' | 'dark'
  primaryColor: string
  backgroundColor: string
  textColor: string
  cardColor: string
}

@Component
struct ThemeProvider {
  @State themeConfig: ThemeConfig = defaultTheme

  @Builder
  renderContent() {
    Column() {
      // 页面内容
    }
    .backgroundColor(this.themeConfig.backgroundColor)
    .width('100%')
    .height('100%')
  }

  build() {
    Column() {
      this.renderContent()
    }
  }
}

7. 动画效果实现

@Component
struct DrawAnimation {
  @State private rotateAngle: number = 0
  @State private scale: number = 1

  // 抽签动画
  private startAnimation() {
    animateTo({
      duration: 1000,
      curve: Curve.EaseInOut,
      onFinish: () => {
        this.handleAnimationComplete()
      }
    }, () => {
      this.rotateAngle = 360
      this.scale = 1.2
    })
  }

  build() {
    Stack() {
      // 动画内容
    }
    .rotate({ x: 0, y: 1, z: 0, angle: this.rotateAngle })
    .scale({ x: this.scale, y: this.scale })
  }
}

8. 统计图表实现

@Component
struct StatisticsChart {
  @State private data: FoodStat[] = []
  private readonly chartWidth: number = 300
  private readonly chartHeight: number = 200

  private calculateBarHeight(count: number): number {
    const maxCount = Math.max(...this.data.map(item => item.count))
    return maxCount === 0 ? 0 : (count / maxCount) * this.chartHeight
  }

  build() {
    Column() {
      Row() {
        ForEach(this.data, (item: FoodStat) => {
          Column() {
            Text(item.count.toString())
              .fontSize(12)
            Column()
              .width(20)
              .height(this.calculateBarHeight(item.count))
              .backgroundColor('#FF4B4B')
            Text(item.name)
              .fontSize(12)
          }
        })
      }
      .width(this.chartWidth)
      .height(this.chartHeight)
    }
  }
}

实现要点

  1. 数据管理

    • 使用单例模式管理全局数据
    • 采用 Data Preferences 实现数据持久化
    • 实现类型安全的数据操作
  2. UI 实现

    • 基于 ArkTS 声明式 UI 开发
    • 自定义组件封装复用
    • 响应式布局适配
  3. 性能优化

    • 使用 LazyForEach 实现虚拟列表
    • 延迟加载非关键资源
    • 优化动画性能
  4. 工程规范

    • 统一的错误处理机制
    • 规范的代码组织结构
    • 完善的类型定义

开发心得

  1. Stage 模型简化了应用架构设计
  2. ArkTS 提供了优秀的开发体验
  3. 组件化开发提高了代码复用率
  4. 性能优化需要注意细节

参考资源

  1. HarmonyOS 应用开发文档

关于作者

专注 HarmonyOS 应用开发,欢迎交流讨论:

  • gitee:https://gitee.com/zhaoyl1/what-to-eat-meta-service/settings#index
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值