基于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、测试、元服务和应用上架分发等。

更多推荐