news 2026/7/7 1:34:04

基于HarmonyOS的情侣美食管理应用开发实战

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
基于HarmonyOS的情侣美食管理应用开发实战

基于HarmonyOS的情侣美食管理应用开发实战(一)- 项目设计与角色管理-1

📖 系列文章目录

  • (一)项目设计与角色管理(本文)
  • (二)菜谱管理与订单系统
  • (三)美食相册与设置功能
  • (四)数据库优化与问题解决

一、项目背景与需求分析

1.1 项目灵感

在情侣日常生活中,经常面临这样的场景:

  • 主厨不知道今天该做什么菜
  • 食客想吃什么但不知道如何表达
  • 做过的美食没有记录,下次想做时找不到
  • 想要记录一起品尝美食的美好时光

基于这些痛点,我开发了一款情侣美食管理应用,帮助情侣更好地规划、记录和分享美食时光。

1.2 核心功能

功能模块主厨角色食客角色
角色管理选择成为主厨,管理菜谱选择成为食客,浏览点餐
菜谱管理添加、编辑、删除菜谱浏览菜谱、查看详情
订单管理接单、完成订单点餐、查看订单状态
美食相册上传美食照片浏览、点赞美食照片
设置页面修改个人信息、清除数据修改个人信息、清除数据

1.3 技术选型

  • 开发框架:HarmonyOS ArkUI(声明式UI)
  • 开发语言:ArkTS(TypeScript扩展)
  • 数据存储:关系型数据库(RDB)
  • 状态管理:@State、@Prop、@Link装饰器
  • UI设计:HarmonyOS设计规范

二、项目架构设计

2.1 目录结构

entry/src/main/ets/ ├── common/ # 公共工具类 │ ├── CommonConstant.ets # 常量定义 │ ├── LogUtil.ets # 日志工具 │ ├── RdbUtil.ets # 数据库工具 │ └── PreferencesUtil.ets # 偏好设置工具 ├── model/ # 数据模型 │ ├── Recipe.ets # 菜谱模型 │ ├── Order.ets # 订单模型 │ ├── GalleryImage.ets # 相册图片模型 │ └── CoupleConfig.ets # 情侣配置模型 ├── pages/ # 页面 │ ├── SplashPage.ets # 启动页 │ ├── RoleSelectPage.ets # 角色选择页 │ ├── ChefHomePage.ets # 主厨首页 │ ├── DinerHomePage.ets # 食客首页 │ ├── RecipeManagePage.ets # 菜谱管理页 │ ├── OrderManagePage.ets # 订单管理页 │ ├── GalleryPage.ets # 美食相册页 │ └── SettingsPage.ets # 设置页 └── entryability/ └── EntryAbility.ets # 应用入口

2.2 数据库设计

2.2.1 情侣配置表(couple_config)
CREATETABLEIFNOTEXISTScouple_config(idINTEGERPRIMARYKEYAUTOINCREMENT,space_nameTEXTNOTNULL,-- 空间名称chef_nameTEXTNOTNULL,-- 主厨昵称diner_nameTEXTNOTNULL,-- 食客昵称create_timeINTEGERNOTNULL-- 创建时间)
2.2.2 菜谱表(recipe)
CREATETABLEIFNOTEXISTSrecipe(idINTEGERPRIMARYKEYAUTOINCREMENT,nameTEXTNOTNULL,-- 菜谱名称typeINTEGERNOTNULL,-- 菜谱类型(0-荤菜 1-素菜 2-汤品 3-主食 4-甜点)imageTEXT,-- 菜谱图片ingredientsTEXT,-- 食材清单stepsTEXT,-- 制作步骤create_timeINTEGERNOTNULL-- 创建时间)
2.2.3 订单表(order_record)
CREATETABLEIFNOTEXISTSorder_record(idINTEGERPRIMARYKEYAUTOINCREMENT,recipe_idINTEGERNOTNULL,-- 菜谱IDrecipe_nameTEXTNOTNULL,-- 菜谱名称recipe_imageTEXT,-- 菜谱图片statusINTEGERDEFAULT0,-- 订单状态(0-待接单 1-制作中 2-已完成)remarkTEXT,-- 备注信息order_timeINTEGERNOTNULL,-- 下单时间accept_timeINTEGER,-- 接单时间complete_timeINTEGER,-- 完成时间FOREIGNKEY(recipe_id)REFERENCESrecipe(id)ONDELETECASCADE)
2.2.4 美食相册表(gallery_img)
CREATETABLEIFNOTEXISTSgallery_img(idINTEGERPRIMARYKEYAUTOINCREMENT,imageTEXTNOTNULL,-- 图片路径descriptionTEXT,-- 图片描述create_timeINTEGERNOTNULL-- 创建时间)

三、角色管理功能实现

3.1 启动页设计

启动页负责:

  1. 初始化数据库
  2. 检查用户角色状态
  3. 路由到对应页面
// SplashPage.ets 核心逻辑asyncaboutToAppear(){// 初始化数据库awaitRdbUtil.init(this.context)// 检查角色状态constrole=awaitPreferencesUtil.get('current_role','')if(role==='chef'){// 主厨角色 -> 跳转主厨首页router.replaceUrl({url:'pages/ChefHomePage'})}elseif(role==='diner'){// 食客角色 -> 跳转食客首页router.replaceUrl({url:'pages/DinerHomePage'})}else{// 无角色 -> 跳转角色选择页router.replaceUrl({url:'pages/RoleSelectPage'})}}

3.2 角色选择页面

角色选择页面功能:

  • 输入空间名称(情侣专属空间)
  • 输入主厨昵称
  • 输入食客昵称
  • 选择角色(主厨/食客)
  • 保存配置并跳转
3.2.1 页面布局
// 核心UI结构Column(){// 标题Text('创建美食空间').fontSize(28).fontWeight(FontWeight.Bold)// 表单输入TextInput({placeholder:'请输入空间名称'}).onChange((value)=>this.spaceName=value)TextInput({placeholder:'请输入主厨昵称'}).onChange((value)=>this.chefName=value)TextInput({placeholder:'请输入食客昵称'}).onChange((value)=>this.dinerName=value)// 角色选择Row(){Button('我是主厨').onClick(()=>this.selectRole('chef'))Button('我是食客').onClick(()=>this.selectRole('diner'))}}
3.2.2 数据保存逻辑
asyncselectRole(role:string){// 1. 构建配置数据constvalues:relationalStore.ValuesBucket={space_name:this.spaceName.trim(),chef_name:this.chefName.trim(),diner_name:this.dinerName.trim(),create_time:Date.now()}// 2. 检查是否已有配置constpredicates=newrelationalStore.RdbPredicates('couple_config')constresultSet=awaitRdbUtil.query(predicates)consthasExisting=resultSet.rowCount>0resultSet.close()// 3. 保存到数据库if(hasExisting){awaitRdbUtil.update('couple_config',values,predicates)}else{awaitRdbUtil.insert('couple_config',values)}// 4. 保存角色到偏好设置awaitPreferencesUtil.put('current_role',role)// 5. 跳转到对应首页consttargetPage=role==='chef'?'pages/ChefHomePage':'pages/DinerHomePage'router.replaceUrl({url:targetPage})}

3.3 角色持久化机制

3.3.1 为什么需要持久化?

用户选择角色后,希望下次打开应用时自动进入对应页面,无需重复选择。这需要将角色信息持久化存储。

3.3.2 持久化方案

使用Preferences存储角色信息:

// PreferencesUtil.etsexportclassPreferencesUtil{privatestaticpreferences:preferences.Preferences|null=null// 初始化staticasyncinit(context:Context){PreferencesUtil.preferences=awaitpreferences.getPreferences(context,'my_preferences')}// 保存数据staticasyncput(key:string,value:string){awaitPreferencesUtil.preferences?.put(key,value)awaitPreferencesUtil.preferences?.flush()}// 读取数据staticasyncget(key:string,defaultValue:string){returnawaitPreferencesUtil.preferences?.get(key,defaultValue)asstring}}

四、首页功能实现

4.1 主厨首页

主厨首页包含四个功能模块:

  • 菜谱管理
  • 订单管理
  • 美食相册
  • 设置
// ChefHomePage.ets 核心结构@Entry@Componentstruct ChefHomePage{@StatecoupleConfig:CoupleConfig={id:0,space_name:'',chef_name:'未命名',diner_name:'未命名',create_time:0}asyncaboutToAppear(){awaitthis.loadCoupleConfig()}asyncloadCoupleConfig(){constpredicates=newrelationalStore.RdbPredicates('couple_config')constresultSet=awaitRdbUtil.query(predicates)if(resultSet.goToFirstRow()){this.coupleConfig={id:resultSet.getLong(resultSet.getColumnIndex('id')),space_name:resultSet.getString(resultSet.getColumnIndex('space_name')),chef_name:resultSet.getString(resultSet.getColumnIndex('chef_name')),diner_name:resultSet.getString(resultSet.getColumnIndex('diner_name')),create_time:resultSet.getLong(resultSet.getColumnIndex('create_time'))}}resultSet.close()}build(){Column(){// 顶部标题栏Row(){Text(`主厨:${this.coupleConfig.chef_name}`).fontSize(18).fontWeight(FontWeight.Bold)Blank()Text(this.coupleConfig.space_name).fontSize(16).fontColor('#666666')}.width('100%').padding(16)// 功能模块网格Grid(){GridItem(){this.MenuItem('菜谱管理',$r('app.media.ic_recipe'))}.onClick(()=>{router.pushUrl({url:'pages/RecipeManagePage'})})GridItem(){this.MenuItem('订单管理',$r('app.media.ic_order'))}.onClick(()=>{router.pushUrl({url:'pages/OrderManagePage'})})GridItem(){this.MenuItem('美食相册',$r('app.media.ic_gallery'))}.onClick(()=>{router.pushUrl({url:'pages/GalleryPage'})})GridItem(){this.MenuItem('设置',$r('app.media.ic_settings'))}.onClick(()=>{router.pushUrl({url:'pages/SettingsPage'})})}.columnsTemplate('1fr 1fr').rowsTemplate('1fr 1fr')}}}

4.2 食客首页

食客首页与主厨首页类似,但功能权限不同:

  • 菜谱浏览(只读)
  • 点餐下单
  • 美食相册
  • 设置

五、遇到的问题与解决方案

5.1 问题:角色选择后显示"未命名"

问题描述:
选择角色并提交后,跳转到首页,左上角显示"主厨:未命名"或"食客:未命名",但数据库中已有数据。

问题分析:

  1. 数据库表结构与代码模型不匹配
  2. 原表缺少space_name字段
  3. 数据插入失败但未报错

解决方案:

步骤1:修正数据库表结构
-- 修改前CREATETABLEIFNOTEXISTScouple_config(idINTEGERPRIMARYKEYAUTOINCREMENT,chef_nameTEXTNOTNULL,diner_nameTEXTNOTNULL,chef_avatarTEXT,diner_avatarTEXT,create_timeINTEGERNOTNULL,update_timeINTEGERNOTNULL)-- 修改后CREATETABLEIFNOTEXISTScouple_config(idINTEGERPRIMARYKEYAUTOINCREMENT,space_nameTEXTNOTNULL,-- ✅ 新增字段chef_nameTEXTNOTNULL,diner_nameTEXTNOTNULL,create_timeINTEGERNOTNULL)
步骤2:添加强制重建数据库逻辑
// RdbUtil.etsstaticasyncinit(context:Context):Promise<void>{// 删除旧数据库强制重建(开发阶段)try{awaitrelationalStore.deleteRdbStore(context,CommonConstant.DB_NAME)LogUtil.info(RdbUtil.TAG,'已删除旧数据库,将重新创建')}catch(err){LogUtil.info(RdbUtil.TAG,'数据库不存在或删除失败,继续创建新数据库')}// 创建新数据库RdbUtil.rdbStore=awaitrelationalStore.getRdbStore(context,config)awaitRdbUtil.createTables()}

5.2 问题:清除缓存后角色丢失

问题描述:
用户清除应用缓存后,preferences被清除,但数据库数据还在,导致角色丢失但配置仍存在。

解决方案:
在角色选择页加载已有配置,自动填充表单:

// RoleSelectPage.etsasyncloadExistingConfig(){constpredicates=newrelationalStore.RdbPredicates('couple_config')constresultSet=awaitRdbUtil.query(predicates)if(resultSet.goToFirstRow()){this.spaceName=resultSet.getString(resultSet.getColumnIndex('space_name'))this.chefName=resultSet.getString(resultSet.getColumnIndex('chef_name'))this.dinerName=resultSet.getString(resultSet.getColumnIndex('diner_name'))LogUtil.info('RoleSelectPage','已加载历史配置')}resultSet.close()}

六、总结

本篇文章介绍了情侣美食管理应用的整体架构设计和角色管理功能的实现。主要内容包括:

  1. 项目架构:清晰的目录结构,模块化设计
  2. 数据库设计:四张核心表,满足业务需求
  3. 角色管理:启动页、角色选择页、持久化机制
  4. 问题解决:数据库表结构修正、强制重建机制

下一篇文章将详细介绍菜谱管理和订单系统的实现,包括:

  • 菜谱的增删改查
  • 图片上传与展示
  • 订单流程管理
  • 状态同步机制

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/7/7 1:28:19

DVWA命令注入漏洞检测与利用实战一课一得

CSDN博文&#xff1a;DVWA命令注入漏洞检测与利用实战一课一得一、文章前言本次Web安全实训我完成了DVWA平台命令执行漏洞的检测与渗透实验&#xff0c;完整复现从漏洞发现、命令拼接探测到反向Shell拿下服务器权限的全过程。过去只在课本上听过命令注入的概念&#xff0c;本次…

作者头像 李华
网站建设 2026/7/7 1:27:48

PCIe-2.2.4 Routing and Addressing Rules(address ID)

2.2.4.1核心设计要点总结(面向RTL架构师) 地址路由only用于 : mem、IO、message(指定时) 设计关注点 关键规则 硬件实现要点 地址提取 根据Fmt决定从3DW还是4DW头提取地址。 使用MUX选择不同的字节拼接路径(表2-7)。 地址范围判断 4GB以下必须用32位格式(发送端)。 发送…

作者头像 李华
网站建设 2026/7/7 1:25:43

嵌入式安全三要素:认证、完整性与保密性

很多工程师把“安全”等同于“加密”&#xff0c;但在嵌入式系统中&#xff0c;安全的优先级通常是&#xff1a; 身份认证&#xff1b;数据完整性&#xff1b;数据保密性。 也就是说&#xff0c;设备首先要确认&#xff1a; 对方是否可信&#xff1b; 数据是否被改过&#x…

作者头像 李华
网站建设 2026/7/7 1:25:22

解决ValueError: too many values to unpack (expected 2)

报错&#xff1a;ValueError: too many values to unpack (expected 2)复现RGCL模型&#xff0c;遇到的问题&#xff0c;该代码里面报错原因是&#xff1a; collate_fn 里的 tokens 是多层嵌套列表&#xff08;每个样本是分词后的 token 列表&#xff0c;batch 打包后变成 [[to…

作者头像 李华
网站建设 2026/7/7 1:23:56

毕设项目 深度学习火焰检测识别(源码+论文)

文章目录 0 前言1 项目运行效果1 基于YOLO的火焰检测与识别2 课题背景3 卷积神经网络3.1 卷积层3.2 池化层3.3 激活函数&#xff1a;3.4 全连接层3.5 使用tensorflow中keras模块实现卷积神经网络 4 YOLOV54.1 网络架构图4.2 输入端4.3 基准网络4.4 Neck网络4.5 Head输出层 5 数…

作者头像 李华