基于HarmonyOS的情侣美食管理应用开发实战(十二)- 双向订单记录查询

📖 系列文章目录

  • (一)项目设计与角色管理
  • (二)菜谱管理与订单系统
  • (三)美食相册与设置功能
  • (四)数据库优化与问题解决
  • (五)数据持久化问题修复
  • (六)图片显示问题修复
  • (七)菜谱详情页面开发
  • (八)收藏功能实现
  • (九)食客菜谱浏览功能
  • (十)点餐清单与提交订单
  • (十一)主厨订单处理功能
  • (十二)双向订单记录查询(本文)

一、功能概述

1.1 需求分析

在情侣美食管理应用中,食客和主厨都需要查看订单记录。本章节将实现双向订单记录查询功能,包括:

  • 食客订单列表:食客查看自己的历史订单
  • 订单详情查看:查看订单的完整信息
  • 时间线展示:展示订单的处理时间线
  • 取消订单功能:食客可取消未处理的订单

1.2 功能特点

双向查询:食客和主厨都能查看订单
详情完整:展示订单和菜谱的完整信息
时间线清晰:直观展示订单处理流程
操作权限明确:根据角色和状态显示操作


二、食客订单列表

2.1 页面状态管理

@Component
struct DinerHomePage {
  @State orders: Order[] = []  // 订单列表
  @State myOrderCount: number = 0  // 订单数量
  
  async initData() {
    // 加载订单列表
    await this.loadOrders()
    
    // 加载订单数量
    await this.loadMyOrderCount()
  }
  
  async loadOrders() {
    try {
      this.orders = await RdbUtil.queryAllOrders()
      LogUtil.info('DinerHomePage', `加载订单列表成功,共 ${this.orders.length}`)
    } catch (err) {
      LogUtil.error('DinerHomePage', `加载订单列表失败: ${err}`)
    }
  }
}

2.2 订单列表UI

【食客订单列表效果图】

@Builder
MyOrdersTab() {
  Column() {
    // 标题栏
    Row() {
      Text('我的订单')
        .fontSize(20)
        .fontWeight(FontWeight.Bold)
        .fontColor(CommonStyle.TEXT_COLOR_PRIMARY)
      
      Blank()
      
      Text(`${this.myOrderCount}`)
        .fontSize(12)
        .fontColor(CommonStyle.TEXT_COLOR_SECONDARY)
    }
    .width('100%')
    .padding(16)
    
    // 空状态提示
    if (this.myOrderCount === 0) {
      Column() {
        Text('🕐')
          .fontSize(80)
          .fontColor(CommonStyle.TEXT_COLOR_HINT)
        
        Text('暂无订单')
          .fontSize(14)
          .fontColor(CommonStyle.TEXT_COLOR_HINT)
          .margin({ top: 16 })
        
        Text('浏览菜谱并发起点餐')
          .fontSize(12)
          .fontColor(CommonStyle.TEXT_COLOR_HINT)
          .margin({ top: 4 })
      }
      .width('100%')
      .layoutWeight(1)
      .justifyContent(FlexAlign.Center)
    } else {
      // 订单列表
      List({ space: 12 }) {
        ForEach(this.orders, (order: Order) => {
          ListItem() {
            this.OrderCard(order)
          }
        }, (order: Order) => order.id.toString())
      }
      .width('100%')
      .layoutWeight(1)
      .padding({ left: 16, right: 16 })
    }
  }
  .width('100%')
  .height('100%')
  .backgroundColor(CommonStyle.BG_COLOR)
}

2.3 订单卡片设计

**【食客订单卡片效果图】
**

@Builder
OrderCard(order: Order) {
  Column() {
    // 订单头部
    Row() {
      Text(order.recipeName)
        .fontSize(16)
        .fontColor(CommonStyle.TEXT_COLOR_PRIMARY)
        .fontWeight(FontWeight.Bold)
        .layoutWeight(1)
      
      Text(this.getOrderStatusText(order.status))
        .fontSize(12)
        .fontColor(CommonStyle.TEXT_COLOR_WHITE)
        .backgroundColor(this.getOrderStatusColor(order.status))
        .borderRadius(CommonStyle.BORDER_RADIUS_ROUND)
        .padding({ left: 12, right: 12, top: 4, bottom: 4 })
    }
    .width('100%')
    
    // 订单信息
    Row() {
      if (order.recipeImage) {
        Image(order.recipeImage)
          .width(60)
          .height(60)
          .borderRadius(CommonStyle.BORDER_RADIUS_MD)
          .objectFit(ImageFit.Cover)
      } else {
        Column() {
          Text('🍳')
            .fontSize(30)
        }
        .width(60)
        .height(60)
        .borderRadius(CommonStyle.BORDER_RADIUS_MD)
        .backgroundColor(CommonStyle.BG_COLOR_GRAY)
        .justifyContent(FlexAlign.Center)
      }
      
      Column() {
        Text(`下单时间: ${this.formatTime(order.orderTime)}`)
          .fontSize(12)
          .fontColor(CommonStyle.TEXT_COLOR_SECONDARY)
        
        if (order.remark) {
          Text(`备注: ${order.remark}`)
            .fontSize(12)
            .fontColor(CommonStyle.TEXT_COLOR_SECONDARY)
            .margin({ top: 4 })
            .maxLines(2)
            .textOverflow({ overflow: TextOverflow.Ellipsis })
        }
      }
      .layoutWeight(1)
      .alignItems(HorizontalAlign.Start)
      .padding({ left: 12 })
    }
    .width('100%')
    .margin({ top: 12 })
  }
  .width('100%')
  .padding(16)
  .backgroundColor(CommonStyle.BG_COLOR_WHITE)
  .borderRadius(CommonStyle.BORDER_RADIUS_MD)
  .onClick(() => {
    router.pushUrl({
      url: 'pages/OrderDetailPage',
      params: { orderId: order.id }
    })
  })
}

三、订单详情页面

3.1 页面结构设计

**【订单详情页面布局图】
**

┌─────────────────────────────┐
│   导航栏(返回 + 标题)      │
├─────────────────────────────┤
│   订单状态卡片               │
│   - 菜名、状态标签           │
│   - 菜谱图片                 │
├─────────────────────────────┤
│   订单信息                   │
│   - 下单时间                 │
│   - 接单时间                 │
│   - 完成时间                 │
│   - 口味备注                 │
├─────────────────────────────┤
│   所需食材                   │
│   - 食材列表                 │
├─────────────────────────────┤
│   制作步骤                   │
│   - 步骤列表                 │
├─────────────────────────────┤
│   操作按钮(可选)           │
└─────────────────────────────┘

3.2 页面状态管理

@Entry
@Component
struct OrderDetailPage {
  @State order: Order | null = null
  @State recipe: Recipe | null = null
  @State currentRole: string = ''
  private orderId: number = 0
  
  async initData() {
    // 获取当前角色
    await RolePermissionUtil.init(this.context!)
    this.currentRole = RolePermissionUtil.getCurrentRole()
    
    // 加载订单信息
    await this.loadOrder()
    
    // 加载菜谱信息
    if (this.order) {
      await this.loadRecipe()
    }
  }
  
  async loadOrder() {
    this.order = await RdbUtil.queryOrderById(this.orderId)
  }
  
  async loadRecipe() {
    if (this.order) {
      this.recipe = await RdbUtil.queryRecipeById(this.order.recipeId)
    }
  }
}

3.3 时间线展示

@Builder
OrderTimeline() {
  Column() {
    // 下单时间
    Row() {
      Text('下单时间:')
        .fontSize(14)
        .fontColor(CommonStyle.TEXT_COLOR_SECONDARY)
      
      Text(this.formatTime(this.order!.orderTime))
        .fontSize(14)
        .fontColor(CommonStyle.TEXT_COLOR_PRIMARY)
    }
    .width('100%')
    
    // 接单时间
    if (this.order!.acceptTime > 0) {
      Row() {
        Text('接单时间:')
          .fontSize(14)
          .fontColor(CommonStyle.TEXT_COLOR_SECONDARY)
        
        Text(this.formatTime(this.order!.acceptTime))
          .fontSize(14)
          .fontColor(CommonStyle.TEXT_COLOR_PRIMARY)
      }
      .width('100%')
      .margin({ top: 8 })
    }
    
    // 完成时间
    if (this.order!.completeTime > 0) {
      Row() {
        Text('完成时间:')
          .fontSize(14)
          .fontColor(CommonStyle.TEXT_COLOR_SECONDARY)
        
        Text(this.formatTime(this.order!.completeTime))
          .fontSize(14)
          .fontColor(CommonStyle.TEXT_COLOR_PRIMARY)
      }
      .width('100%')
      .margin({ top: 8 })
    }
  }
  .width('100%')
  .alignItems(HorizontalAlign.Start)
}

四、取消订单功能

4.1 操作按钮显示规则

@Builder
OrderActions() {
  // 食客可取消未处理的订单
  if (this.currentRole === CommonConstant.ROLE_DINER && 
      this.order!.status === CommonConstant.ORDER_STATUS_PENDING) {
    Column() {
      Button('取消订单')
        .fontSize(16)
        .fontColor(CommonStyle.TEXT_COLOR_WHITE)
        .backgroundColor('#FF3B30')
        .borderRadius(CommonStyle.BORDER_RADIUS_MD)
        .width('100%')
        .height(50)
        .onClick(() => {
          this.cancelOrder()
        })
    }
    .width('100%')
    .padding(16)
    .margin({ top: 12, bottom: 20 })
  }
}

4.2 取消订单逻辑

async cancelOrder() {
  if (!this.order) {
    return
  }
  
  try {
    this.order.status = CommonConstant.ORDER_STATUS_CANCELLED
    const success = await RdbUtil.updateOrder(this.order)
    
    if (success) {
      promptAction.showToast({
        message: '订单已取消',
        duration: 2000
      })
      router.back()
    }
  } catch (err) {
    LogUtil.error('OrderDetailPage', `取消订单失败: ${err}`)
    promptAction.showToast({
      message: '操作失败',
      duration: 2000
    })
  }
}

五、数据库查询方法

5.1 根据ID查询订单

static async queryOrderById(id: number): Promise<Order | null> {
  try {
    const predicates = new relationalStore.RdbPredicates('order')
    predicates.equalTo('id', id)
    
    const resultSet = await this.rdbStore.query(predicates)
    
    if (resultSet.goToFirstRow()) {
      const order = Order.fromJson({
        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')),
        create_time: resultSet.getLong(resultSet.getColumnIndex('create_time')),
        update_time: resultSet.getLong(resultSet.getColumnIndex('update_time'))
      })
      
      resultSet.close()
      return order
    }
    
    resultSet.close()
    return null
  } catch (err) {
    LogUtil.error('RdbUtil', `查询订单失败: ${err}`)
    return null
  }
}

5.2 根据状态查询订单

static async queryOrdersByStatus(status: number): Promise<Order[]> {
  try {
    const predicates = new relationalStore.RdbPredicates('order')
    predicates.equalTo('status', status)
    predicates.orderByDesc('order_time')
    
    const resultSet = await this.rdbStore.query(predicates)
    const orders: Order[] = []
    
    while (resultSet.goToNextRow()) {
      const order = Order.fromJson({...})
      orders.push(order)
    }
    
    resultSet.close()
    return orders
  } catch (err) {
    LogUtil.error('RdbUtil', `查询订单失败: ${err}`)
    return []
  }
}

六、技术要点总结

6.1 角色权限控制

// 根据角色显示不同的操作
if (this.currentRole === CommonConstant.ROLE_DINER) {
  // 食客:可取消未处理的订单
} else if (this.currentRole === CommonConstant.ROLE_CHEF) {
  // 主厨:可接受、制作、完成订单
}

6.2 时间格式化

formatTime(timestamp: number): string {
  if (!timestamp) {
    return ''
  }
  const date = new Date(timestamp)
  const year = date.getFullYear()
  const month = (date.getMonth() + 1).toString().padStart(2, '0')
  const day = date.getDate().toString().padStart(2, '0')
  const hour = date.getHours().toString().padStart(2, '0')
  const minute = date.getMinutes().toString().padStart(2, '0')
  return `${year}-${month}-${day} ${hour}:${minute}`
}

6.3 数据关联查询

// 先查询订单
this.order = await RdbUtil.queryOrderById(this.orderId)

// 再根据订单中的recipeId查询菜谱
if (this.order) {
  this.recipe = await RdbUtil.queryRecipeById(this.order.recipeId)
}

七、总结

通过本篇文章,我们完成了双向订单记录查询功能的开发✅ 食客订单列表:展示历史订单,支持点击查看详情
订单详情完整:展示订单信息、菜谱信息、时间线
取消订单功能:食客可取消未处理的订单
角色权限明确:根据角色显示不同的操作

至此,情侣美食管理应用的核心功能已全部实现完成!


八、系列文章总结

通过十二篇文章的讲解,我们完整实现了情侣美食管理应用的所有核心功能:

8.1 功能模块

模块功能文章
角色管理角色选择、权限控制(一)
菜谱管理菜谱CRUD、食材步骤(二)
美食相册图片上传、相册展示(三)
数据库RDB操作、数据持久化(四)(五)
图片显示图片加载、缓存优化(六)
菜谱详情详情展示、收藏功能(七)(八)
食客浏览搜索筛选、随机点菜(九)
点餐下单备注输入、订单提交(十)
主厨处理订单管理、状态流转(十一)
订单查询双向查询、详情展示(十二)

8.2 技术栈

  • 开发语言:ArkTS
  • UI框架:ArkUI
  • 数据库:关系型数据库(RDB)
  • 状态管理:@State、@Prop、@Link
  • 路由导航:router模块
  • 图片处理:Image组件、picker模块

8.3 项目亮✅ 角色分离:主厨和食客功能清晰分离

数据完整:菜谱、订单、相册数据完整管理
交互友好:卡片式布局,操作便捷
状态清晰:订单状态流转明确
权限控制:根据角色显示不同功能


Logo

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

更多推荐