基于HarmonyOS的情侣美食管理应用开发实战(二)- 菜谱管理与订单系统
·
基于HarmonyOS的情侣美食管理应用开发实战(二)- 菜谱管理与订单系统
📖 系列文章目录
- (一)项目设计与角色管理
- (二)菜谱管理与订单系统(本文)
- (三)美食相册与设置功能
- (四)数据库优化与问题解决
一、菜谱管理功能实现
1.1 功能概述
菜谱管理是本应用的核心功能之一,主厨可以:
- 添加新菜谱(名称、类型、图片、食材、步骤)
- 编辑已有菜谱
- 删除菜谱
- 查看菜谱详情
食客可以:
- 浏览所有菜谱
- 查看菜谱详情
- 根据菜谱下单
1.2 菜谱管理页面

1.2.1 页面结构
@Entry
@Component
struct RecipeManagePage {
@State recipeList: Recipe[] = [] // 菜谱列表
@State isRefreshing: boolean = false // 下拉刷新状态
aboutToAppear() {
this.loadRecipes()
}
// 加载菜谱列表
async loadRecipes() {
const predicates = new relationalStore.RdbPredicates('recipe')
predicates.orderByDesc('create_time')
const resultSet = await RdbUtil.query(predicates)
this.recipeList = []
while (resultSet.goToNextRow()) {
const recipe: Recipe = {
id: resultSet.getLong(resultSet.getColumnIndex('id')),
name: resultSet.getString(resultSet.getColumnIndex('name')),
type: resultSet.getLong(resultSet.getColumnIndex('type')),
image: resultSet.getString(resultSet.getColumnIndex('image')),
ingredients: resultSet.getString(resultSet.getColumnIndex('ingredients')),
steps: resultSet.getString(resultSet.getColumnIndex('steps')),
create_time: resultSet.getLong(resultSet.getColumnIndex('create_time'))
}
this.recipeList.push(recipe)
}
resultSet.close()
}
build() {
Column() {
// 顶部标题栏
Row() {
Text('菜谱管理')
.fontSize(20)
.fontWeight(FontWeight.Bold)
Blank()
Button('添加菜谱')
.onClick(() => {
router.pushUrl({ url: 'pages/RecipeAddPage' })
})
}
.width('100%')
.padding(16)
// 菜谱列表
List() {
ForEach(this.recipeList, (recipe: Recipe) => {
ListItem() {
this.RecipeItem(recipe)
}
})
}
.width('100%')
.layoutWeight(1)
.refresh({
refreshing: $$this.isRefreshing,
onRefresh: async () => {
await this.loadRecipes()
this.isRefreshing = false
}
})
}
}
@Builder
RecipeItem(recipe: Recipe) {
Row() {
// 菜谱图片
Image(recipe.image || $r('app.media.default_recipe'))
.width(80)
.height(80)
.borderRadius(8)
// 菜谱信息
Column() {
Text(recipe.name)
.fontSize(16)
.fontWeight(FontWeight.Bold)
Text(this.getTypeName(recipe.type))
.fontSize(14)
.fontColor('#666666')
.margin({ top: 4 })
}
.alignItems(HorizontalAlign.Start)
.layoutWeight(1)
.margin({ left: 12 })
// 操作按钮
Row() {
Button('编辑')
.onClick(() => {
router.pushUrl({
url: 'pages/RecipeEditPage',
params: { recipeId: recipe.id }
})
})
Button('删除')
.onClick(() => {
this.deleteRecipe(recipe.id)
})
}
}
.width('100%')
.padding(12)
.backgroundColor('#FFFFFF')
.borderRadius(8)
.margin({ bottom: 8 })
}
}
1.2.2 添加菜谱
// RecipeAddPage.ets
@Entry
@Component
struct RecipeAddPage {
@State name: string = ''
@State type: number = 0
@State image: string = ''
@State ingredients: string = ''
@State steps: string = ''
// 选择图片
async selectImage() {
const photoSelectOptions = new picker.PhotoSelectOptions()
photoSelectOptions.MIMEType = picker.PhotoViewMIMETypes.IMAGE_TYPE
photoSelectOptions.maxSelectNumber = 1
const photoPicker = new picker.PhotoViewPicker()
const photoSelectResult = await photoPicker.select(photoSelectOptions)
if (photoSelectResult.photoUris.length > 0) {
this.image = photoSelectResult.photoUris[0]
}
}
// 保存菜谱
async saveRecipe() {
if (!this.name.trim()) {
prompt.showToast({ message: '请输入菜谱名称' })
return
}
const values: relationalStore.ValuesBucket = {
name: this.name.trim(),
type: this.type,
image: this.image,
ingredients: this.ingredients.trim(),
steps: this.steps.trim(),
create_time: Date.now()
}
const rowId = await RdbUtil.insert('recipe', values)
if (rowId > 0) {
prompt.showToast({ message: '添加成功' })
router.back()
}
}
build() {
Column() {
// 表单输入
TextInput({ placeholder: '菜谱名称' })
.onChange((value) => this.name = value)
// 菜谱类型选择
Row() {
ForEach(['荤菜', '素菜', '汤品', '主食', '甜点'], (item: string, index: number) => {
Button(item)
.type(this.type === index ? ButtonType.Capsule : ButtonType.Normal)
.onClick(() => this.type = index)
})
}
// 图片选择
Image(this.image || $r('app.media.default_recipe'))
.width(200)
.height(200)
.onClick(() => this.selectImage())
TextArea({ placeholder: '食材清单' })
.onChange((value) => this.ingredients = value)
TextArea({ placeholder: '制作步骤' })
.onChange((value) => this.steps = value)
Button('保存')
.onClick(() => this.saveRecipe())
}
}
}
二、订单管理功能实现
2.1 功能概述
订单管理实现了主厨与食客之间的互动:
- 食客:浏览菜谱 → 下单 → 查看订单状态
- 主厨:接单 → 制作 → 完成
2.2 订单管理页面

2.2.1 订单状态定义
// 订单状态枚举
enum OrderStatus {
PENDING = 0, // 待接单
ACCEPTED = 1, // 制作中
COMPLETED = 2 // 已完成
}
// 状态文本映射
const statusText = {
[OrderStatus.PENDING]: '待接单',
[OrderStatus.ACCEPTED]: '制作中',
[OrderStatus.COMPLETED]: '已完成'
}
// 状态颜色映射
const statusColor = {
[OrderStatus.PENDING]: '#FF9800',
[OrderStatus.ACCEPTED]: '#2196F3',
[OrderStatus.COMPLETED]: '#4CAF50'
}
2.2.2 主厨订单管理
@Entry
@Component
struct OrderManagePage {
@State orderList: Order[] = []
@State currentTab: number = 0 // 0-待处理 1-已完成
aboutToAppear() {
this.loadOrders()
}
async loadOrders() {
const predicates = new relationalStore.RdbPredicates('order_record')
predicates.orderByDesc('order_time')
if (this.currentTab === 0) {
// 待处理订单
predicates.lessThan('status', 2)
} else {
// 已完成订单
predicates.equalTo('status', 2)
}
const resultSet = await RdbUtil.query(predicates)
this.orderList = []
while (resultSet.goToNextRow()) {
const order: Order = {
id: resultSet.getLong(resultSet.getColumnIndex('id')),
recipe_id: resultSet.getLong(resultSet.getColumnIndex('recipe_id')),
recipe_name: resultSet.getString(resultSet.getColumnIndex('recipe_name')),
recipe_image: resultSet.getString(resultSet.getColumnIndex('recipe_image')),
status: resultSet.getLong(resultSet.getColumnIndex('status')),
remark: resultSet.getString(resultSet.getColumnIndex('remark')),
order_time: resultSet.getLong(resultSet.getColumnIndex('order_time')),
accept_time: resultSet.getLong(resultSet.getColumnIndex('accept_time')),
complete_time: resultSet.getLong(resultSet.getColumnIndex('complete_time'))
}
this.orderList.push(order)
}
resultSet.close()
}
// 接单
async acceptOrder(orderId: number) {
const values: relationalStore.ValuesBucket = {
status: OrderStatus.ACCEPTED,
accept_time: Date.now()
}
const predicates = new relationalStore.RdbPredicates('order_record')
predicates.equalTo('id', orderId)
await RdbUtil.update('order_record', values, predicates)
prompt.showToast({ message: '已接单' })
await this.loadOrders()
}
// 完成订单
async completeOrder(orderId: number) {
const values: relationalStore.ValuesBucket = {
status: OrderStatus.COMPLETED,
complete_time: Date.now()
}
const predicates = new relationalStore.RdbPredicates('order_record')
predicates.equalTo('id', orderId)
await RdbUtil.update('order_record', values, predicates)
prompt.showToast({ message: '订单已完成' })
await this.loadOrders()
}
build() {
Column() {
// 标签切换
Tabs() {
TabContent() {
this.OrderList()
}
.tabBar('待处理')
TabContent() {
this.OrderList()
}
.tabBar('已完成')
}
.onChange((index: number) => {
this.currentTab = index
this.loadOrders()
})
}
}
@Builder
OrderList() {
List() {
ForEach(this.orderList, (order: Order) => {
ListItem() {
Column() {
Row() {
Image(order.recipe_image || $r('app.media.default_recipe'))
.width(60)
.height(60)
.borderRadius(8)
Column() {
Text(order.recipe_name)
.fontSize(16)
.fontWeight(FontWeight.Bold)
Text(statusText[order.status])
.fontSize(14)
.fontColor(statusColor[order.status])
.margin({ top: 4 })
}
.alignItems(HorizontalAlign.Start)
.layoutWeight(1)
.margin({ left: 12 })
}
// 操作按钮
if (order.status === OrderStatus.PENDING) {
Button('接单')
.onClick(() => this.acceptOrder(order.id))
} else if (order.status === OrderStatus.ACCEPTED) {
Button('完成')
.onClick(() => this.completeOrder(order.id))
}
}
.padding(12)
.backgroundColor('#FFFFFF')
.borderRadius(8)
}
})
}
}
}
2.2.3 食客下单
// 食客在菜谱详情页下单
async placeOrder(recipe: Recipe) {
const values: relationalStore.ValuesBucket = {
recipe_id: recipe.id,
recipe_name: recipe.name,
recipe_image: recipe.image || '',
status: OrderStatus.PENDING,
remark: this.remark.trim(),
order_time: Date.now()
}
const rowId = await RdbUtil.insert('order_record', values)
if (rowId > 0) {
prompt.showToast({ message: '下单成功,等待主厨接单' })
router.back()
}
}
2.3 订单流程图
食客下单
│
▼
┌──────────┐
│ 待接单 │
└────┬─────┘
│
▼ 主厨接单
┌──────────┐
│ 制作中 │
└────┬─────┘
│
▼ 主厨完成
┌──────────┐
│ 已完成 │
└──────────┘
三、关键技术点
3.1 图片选择与存储
使用 HarmonyOS 的 PhotoViewPicker 选择图片:
async selectImage(): Promise<string> {
const photoSelectOptions = new picker.PhotoSelectOptions()
photoSelectOptions.MIMEType = picker.PhotoViewMIMETypes.IMAGE_TYPE
photoSelectOptions.maxSelectNumber = 1
const photoPicker = new picker.PhotoViewPicker()
const photoSelectResult = await photoPicker.select(photoSelectOptions)
return photoSelectResult.photoUris[0] || ''
}
3.2 下拉刷新
使用 List 组件的 refresh 属性实现下拉刷新:
List() {
// ...
}
.refresh({
refreshing: $$this.isRefreshing,
onRefresh: async () => {
await this.loadData()
this.isRefreshing = false
}
})
3.3 数据库关联查询
订单表通过外键关联菜谱表:
// 创建订单时保存菜谱信息
const values = {
recipe_id: recipe.id, // 外键
recipe_name: recipe.name, // 冗余存储,提高查询效率
recipe_image: recipe.image, // 冗余存储
// ...
}
四、优化与改进
4.1 性能优化
- 图片懒加载:使用 LazyForEach 替代 ForEach
- 分页加载:订单列表支持分页
- 缓存机制:菜谱列表缓存到内存
4.2 用户体验优化
- 空状态展示:列表为空时显示友好提示
- 加载动画:数据加载时显示 Loading
- 操作确认:删除操作添加确认对话框
五、总结
本篇文章详细介绍了菜谱管理和订单系统的实现,包括:
- 菜谱管理:增删改查、图片上传
- 订单系统:下单、接单、完成流程
- 技术要点:图片选择、下拉刷新、数据库关联
下一篇文章将介绍美食相册和设置功能的实现。
更多推荐


所有评论(0)