基于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 性能优化

  1. 图片懒加载:使用 LazyForEach 替代 ForEach
  2. 分页加载:订单列表支持分页
  3. 缓存机制:菜谱列表缓存到内存

4.2 用户体验优化

  1. 空状态展示:列表为空时显示友好提示
  2. 加载动画:数据加载时显示 Loading
  3. 操作确认:删除操作添加确认对话框

五、总结

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

  1. 菜谱管理:增删改查、图片上传
  2. 订单系统:下单、接单、完成流程
  3. 技术要点:图片选择、下拉刷新、数据库关联

下一篇文章将介绍美食相册和设置功能的实现。


Logo

讨论HarmonyOS开发技术,专注于API与组件、DevEco Studio、测试、元服务和应用上架分发等。

更多推荐