HarmonyOS ArkTS 实战:实现一个校园食堂点餐与取餐应用

项目效果

本文使用 HarmonyOS 和 ArkTS 实现一个校园食堂点餐与取餐应用。

应用可以浏览食堂菜品,在线点餐下单,查看取餐号和排队进度,管理购物车,并提供食堂筛选、订单状态查看和消费统计等功能。

项目使用 DevEco Studio 开发,适配 API 23 及以上版本。

运行效果

校园食堂点餐与取餐应用

功能介绍

本项目实现了以下功能:

  • 浏览各食堂菜品
  • 查看菜品价格和图片描述
  • 选择菜品加入购物车
  • 调整购买数量
  • 选择食堂和取餐窗口
  • 提交订单
  • 查看取餐号
  • 查看排队进度
  • 确认取餐完成
  • 按订单状态筛选
  • 统计今日订单、待取餐、已完成数量
  • 统计消费金额
  • 取消未支付订单

定义数据结构

首先定义菜品和订单的数据结构:

interface FoodItem {
  id: number;
  name: string;
  price: number;
  canteen: string;
  window: string;
  description: string;
  category: string;
}

interface OrderItem {
  foodId: number;
  foodName: string;
  quantity: number;
  price: number;
}

interface FoodOrder {
  id: number;
  orderNumber: string;
  canteen: string;
  window: string;
  items: OrderItem[];
  totalPrice: number;
  status: string;
  pickupNumber: number;
  currentNumber: number;
  orderTime: string;
  remark: string;
}

字段说明如下:

  • FoodItem:菜品信息
    • id:菜品编号
    • name:菜品名称
    • price:价格
    • canteen:所属食堂
    • window:窗口
    • description:菜品描述
    • category:分类
  • FoodOrder:订单信息
    • id:订单编号
    • orderNumber:订单号
    • canteen:食堂
    • window:取餐窗口
    • items:订单菜品列表
    • totalPrice:总价
    • status:订单状态(待支付/制作中/待取餐/已完成/已取消)
    • pickupNumber:取餐号
    • currentNumber:当前叫号
    • orderTime:下单时间
    • remark:备注

初始化页面状态

使用 @State 保存购物车、食堂选择、筛选条件和订单数据:

@State private selectedCanteen: string = '一食堂';
@State private selectedCategory: string = '全部';
@State private filterStatus: string = '全部';
@State private remarkText: string = '';
@State private nextOrderId: number = 5;
@State private nextPickupNumber: number = 1024;
@State private cart: Map<number, number> = new Map();

准备一些初始菜品数据:

@State private foods: FoodItem[] = [
  {
    id: 1,
    name: '黄焖鸡米饭',
    price: 15,
    canteen: '一食堂',
    window: '三楼3号',
    description: '经典黄焖鸡,配米饭,可加辣',
    category: '主食'
  },
  {
    id: 2,
    name: '番茄鸡蛋面',
    price: 10,
    canteen: '一食堂',
    window: '二楼1号',
    description: '手工面,番茄鸡蛋汤底',
    category: '面食'
  },
  {
    id: 3,
    name: '麻辣香锅',
    price: 18,
    canteen: '二食堂',
    window: '一楼5号',
    description: '自选菜品,麻辣鲜香',
    category: '主食'
  },
  {
    id: 4,
    name: '珍珠奶茶',
    price: 8,
    canteen: '二食堂',
    window: '水吧',
    description: '冰/热可选,三分糖正常冰',
    category: '饮品'
  },
  {
    id: 5,
    name: '烤肉饭',
    price: 14,
    canteen: '三食堂',
    window: '二楼2号',
    description: '土耳其烤肉,配蔬菜沙拉',
    category: '主食'
  },
  {
    id: 6,
    name: '小笼包',
    price: 8,
    canteen: '一食堂',
    window: '一楼早餐',
    description: '一笼8个,鲜肉馅',
    category: '早餐'
  }
];

准备一些初始订单数据:

@State private orders: FoodOrder[] = [
  {
    id: 1,
    orderNumber: '20260717001',
    canteen: '一食堂',
    window: '三楼3号',
    items: [{ foodId: 1, foodName: '黄焖鸡米饭', quantity: 1, price: 15 }],
    totalPrice: 15,
    status: '待取餐',
    pickupNumber: 1021,
    currentNumber: 1018,
    orderTime: '2026-07-17 12:05',
    remark: '微辣'
  },
  {
    id: 2,
    orderNumber: '20260717002',
    canteen: '二食堂',
    window: '水吧',
    items: [{ foodId: 4, foodName: '珍珠奶茶', quantity: 2, price: 8 }],
    totalPrice: 16,
    status: '制作中',
    pickupNumber: 1022,
    currentNumber: 1019,
    orderTime: '2026-07-17 12:10',
    remark: '少冰'
  },
  {
    id: 3,
    orderNumber: '20260716003',
    canteen: '三食堂',
    window: '二楼2号',
    items: [{ foodId: 5, foodName: '烤肉饭', quantity: 1, price: 14 }],
    totalPrice: 14,
    status: '已完成',
    pickupNumber: 998,
    currentNumber: 1023,
    orderTime: '2026-07-16 18:30',
    remark: ''
  }
];

购物车操作

用户可以将菜品加入购物车,调整数量:

private addToCart(foodId: number): void {
  const current = this.cart.get(foodId) || 0;
  this.cart.set(foodId, current + 1);
  this.cart = new Map(this.cart);
}

private decreaseFromCart(foodId: number): void {
  const current = this.cart.get(foodId) || 0;
  if (current > 1) {
    this.cart.set(foodId, current - 1);
  } else {
    this.cart.delete(foodId);
  }
  this.cart = new Map(this.cart);
}

private getCartQuantity(foodId: number): number {
  return this.cart.get(foodId) || 0;
}

private getCartTotal(): number {
  let total = 0;
  this.cart.forEach((quantity: number, foodId: number) => {
    const food = this.foods.find((f: FoodItem) => f.id === foodId);
    if (food) {
      total += food.price * quantity;
    }
  });
  return total;
}

private getCartCount(): number {
  let count = 0;
  this.cart.forEach((quantity: number) => {
    count += quantity;
  });
  return count;
}

支持增加、减少菜品数量,自动计算总价和总数量。

提交订单

购物车确认后可以提交订单:

private submitOrder(): void {
  if (this.cart.size === 0) {
    return;
  }

  const orderItems: OrderItem[] = [];
  let canteen = '';
  let window = '';
  
  this.cart.forEach((quantity: number, foodId: number) => {
    const food = this.foods.find((f: FoodItem) => f.id === foodId);
    if (food) {
      orderItems.push({
        foodId: food.id,
        foodName: food.name,
        quantity,
        price: food.price
      });
      canteen = food.canteen;
      window = food.window;
    }
  });

  const now = new Date();
  const timeStr = `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, '0')}-${String(now.getDate()).padStart(2, '0')} ${String(now.getHours()).padStart(2, '0')}:${String(now.getMinutes()).padStart(2, '0')}`;
  const orderNum = `20260717${String(this.nextOrderId).padStart(3, '0')}`;

  const order: FoodOrder = {
    id: this.nextOrderId,
    orderNumber: orderNum,
    canteen,
    window,
    items: orderItems,
    totalPrice: this.getCartTotal(),
    status: '制作中',
    pickupNumber: this.nextPickupNumber,
    currentNumber: this.nextPickupNumber - 3,
    orderTime: timeStr,
    remark: this.remarkText.trim()
  };

  this.orders = [order, ...this.orders];
  this.nextOrderId += 1;
  this.nextPickupNumber += 1;
  this.cart = new Map();
  this.remarkText = '';
}

新订单默认处于"制作中"状态,自动生成取餐号。

订单状态流转

食堂订单按照以下流程处理:

制作中 -> 待取餐 -> 已完成
   |
   -> 已取消

使用以下方法更新订单状态:

private setReadyForPickup(id: number): void {
  this.orders = this.orders.map((order: FoodOrder) => {
    if (order.id === id && order.status === '制作中') {
      return {
        id: order.id,
        orderNumber: order.orderNumber,
        canteen: order.canteen,
        window: order.window,
        items: order.items,
        totalPrice: order.totalPrice,
        status: '待取餐',
        pickupNumber: order.pickupNumber,
        currentNumber: order.pickupNumber,
        orderTime: order.orderTime,
        remark: order.remark
      };
    }
    return order;
  });
}

private completeOrder(id: number): void {
  this.orders = this.orders.map((order: FoodOrder) => {
    if (order.id === id && order.status === '待取餐') {
      return {
        id: order.id,
        orderNumber: order.orderNumber,
        canteen: order.canteen,
        window: order.window,
        items: order.items,
        totalPrice: order.totalPrice,
        status: '已完成',
        pickupNumber: order.pickupNumber,
        currentNumber: order.currentNumber,
        orderTime: order.orderTime,
        remark: order.remark
      };
    }
    return order;
  });
}

制作完成后变为待取餐,用户取餐后变为已完成。

模拟叫号进度

可以模拟排队叫号进度:

private updateCallingNumber(): void {
  this.orders = this.orders.map((order: FoodOrder) => {
    if (order.status === '制作中' && order.currentNumber < order.pickupNumber) {
      return {
        id: order.id,
        orderNumber: order.orderNumber,
        canteen: order.canteen,
        window: order.window,
        items: order.items,
        totalPrice: order.totalPrice,
        status: order.status,
        pickupNumber: order.pickupNumber,
        currentNumber: order.currentNumber + 1,
        orderTime: order.orderTime,
        remark: order.remark
      };
    }
    return order;
  });
}

每次调用当前叫号加1,模拟排队进度。

筛选菜品和订单

页面支持按食堂和分类筛选菜品,按状态筛选订单:

private getFilteredFoods(): FoodItem[] {
  return this.foods.filter((food: FoodItem) => {
    const canteenMatch = food.canteen === this.selectedCanteen;
    const categoryMatch = this.selectedCategory === '全部' || food.category === this.selectedCategory;
    return canteenMatch && categoryMatch;
  });
}

private getFilteredOrders(): FoodOrder[] {
  if (this.filterStatus === '全部') {
    return this.orders;
  }
  return this.orders.filter(
    (order: FoodOrder) => order.status === this.filterStatus
  );
}

食堂选择按钮封装:

@Builder
CanteenButton(name: string) {
  Button(name)
    .height(36)
    .padding({ left: 16, right: 16 })
    .fontSize(14)
    .fontColor(this.selectedCanteen === name ? Color.White : '#344054')
    .backgroundColor(
      this.selectedCanteen === name ? '#DC2626' : '#FEE2E2'
    )
    .borderRadius(18)
    .onClick(() => {
      this.selectedCanteen = name;
    });
}

统计订单和消费

统计各类订单数量和总消费:

private getOrderCount(status: string): number {
  if (status === '全部') {
    return this.orders.length;
  }
  return this.orders.filter((o: FoodOrder) => o.status === status).length;
}

private getTotalSpent(): number {
  return this.orders
    .filter((o: FoodOrder) => o.status === '已完成')
    .reduce((sum: number, o: FoodOrder) => sum + o.totalPrice, 0);
}

顶部统计区域展示今日订单、待取餐、已完成和总消费:

this.StatCard(
  '今日订单',
  `${this.getOrderCount('全部')}`,
  '#DC2626',
  '#FEF2F2'
);
this.StatCard(
  '待取餐',
  `${this.getOrderCount('待取餐')}`,
  '#D97706',
  '#FFF7E8'
);
this.StatCard(
  '已完成',
  `${this.getOrderCount('已完成')}`,
  '#059669',
  '#ECFDF5'
);
this.StatCard(
  '总消费',
  `¥${this.getTotalSpent()}`,
  '#7C3AED',
  '#F5F3FF'
);

设置状态颜色

不同订单状态使用不同颜色:

private getStatusColor(status: string): ResourceColor {
  if (status === '制作中') {
    return '#2563EB';
  }
  if (status === '待取餐') {
    return '#D97706';
  }
  if (status === '已完成') {
    return '#059669';
  }
  return '#6B7280';
}

private getStatusBgColor(status: string): ResourceColor {
  if (status === '制作中') {
    return '#DBEAFE';
  }
  if (status === '待取餐') {
    return '#FEF3C7';
  }
  if (status === '已完成') {
    return '#DCFCE7';
  }
  return '#F3F4F6';
}

颜色含义如下:

  • 蓝色:制作中
  • 橙色:待取餐
  • 绿色:已完成
  • 灰色:已取消
  • 红色:页面主题色

使用网格展示菜品

使用 Grid 展示菜品列表:

Grid() {
  ForEach(
    this.getFilteredFoods(),
    (food: FoodItem) => {
      GridItem() {
        this.FoodCard(food)
      }
    },
    (food: FoodItem) => food.id.toString()
  );
}
.columnsTemplate('1fr 1fr')
.rowsGap(12)
.columnsGap(12)
.width('100%')

菜品使用两列网格布局,每个菜品卡片包含名称、价格、描述和加入购物车按钮。

页面设计说明

应用使用红色作为主题色,符合餐饮类应用的视觉习惯。

页面主要分为以下区域:

  1. 顶部标题和食堂选择
  2. 订单数据统计区域
  3. 菜品分类和菜品网格
  4. 购物车悬浮栏
  5. 订单列表

页面采用浅灰色背景和白色卡片。菜品价格使用红色字体突出显示,订单状态使用对应颜色标签,取餐号和排队进度使用大号字体展示,便于快速查看。

SDK 配置

本项目使用 HarmonyOS API 24,满足 API 23 及以上要求:

{
  "name": "default",
  "compatibleSdkVersion": "6.1.1(24)",
  "runtimeOS": "HarmonyOS",
  "targetSdkVersion": "6.1.1(24)",
  "compileSdkVersion": "6.1.1(24)"
}

entry 模块中的运行系统也要保持一致:

{
  "apiType": "stageMode",
  "targets": [
    {
      "name": "default",
      "runtimeOS": "HarmonyOS"
    }
  ]
}

运行项目

使用 DevEco Studio 打开项目,然后找到:

entry/src/main/ets/pages/Index.ets

等待项目同步完成,点击右侧的 Preview 按钮即可查看应用效果。

项目总结

本文使用 HarmonyOS 和 ArkTS 实现了一个校园食堂点餐与取餐应用。

项目实现了菜品浏览、购物车管理、数量调整、在线下单、取餐号生成、排队进度、订单状态流转、消费统计等功能。

通过这个项目可以掌握:

  • ArkTS 接口定义
  • @State 和 Map 状态管理
  • Grid 网格布局展示菜品
  • ListForEach 列表渲染
  • mapfilter 数组操作
  • 购物车逻辑实现
  • 排队叫号模拟
  • 自定义 @Builder 组件
  • HarmonyOS 餐饮类应用布局

后续还可以加入菜品图片、在线支付、评价系统、营养成分显示、预约点餐、拼单功能和食堂人流实时显示等功能。

Logo

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

更多推荐