基于鸿蒙HarmonyOS_NEXT的美团点餐案例

工程目录:

-pages

        -MeiTuan

                -api 请求

                -components 组件

                -models 类型

                -utils 工具类

                -MTIndex.ets(Page) 主页面

MTIndex

import MTBottom from './components/MTBottom'
import MTCart from './components/MTCart'
import MTMain from './components/MTMain'
import MTTop from './components/MTTop'
import { FoodItem } from './models'
import { CartStore } from './utils'
import { promptAction } from '@kit.ArkUI'

//主页面:
@Entry
  @Component
  struct MTIndex {
    @Provide//判断购物车是否出来的一个变量
    showCart:boolean=false

    @Provide//接收CartStore.getCarts()传过来的购物车数据,然后传给MTAddCut,MTCart,MTBottom的consume
    userCart: FoodItem[] = []

    // @StorageProp("user_cart")//创建一个data来拿到utils的数据,watch监听utils的数据变化
    // @Watch("updateCart")
    // data:FoodItem[]=[]

    aboutToAppear(): void {//一打开页面就接收购物车,只执行一次
      this.userCart = CartStore.getCarts()
      getContext().eventHub.on("change_cart",()=>{  //监听线程间通信的更新
        this.userCart = CartStore.getCarts()//重新赋值给userCart
      })
    }

    // updateCart(){//监听到data变化,进行持久化数据的更新
    //   this.userCart = CartStore.getCarts()
    // }

    build() {
      Column() {
        Stack({ alignContent: Alignment.Bottom }) {
          Column() {
            MTTop()
            MTMain()
          }
          .height("100%")
          if (this.showCart){  //如果showCart=1,打开购物车MTCart。
            MTCart()
          }
          MTBottom()//底层组件
        }.layoutWeight(1)
      }
      .width('100%')
        .height("100%")
        .backgroundColor($r("app.color.white"))
    }
  }

API/index

import { http } from '@kit.NetworkKit'
import { promptAction } from '@kit.ArkUI'
import { Category } from '../models'

//发情网络请求方法 api/index.ets 使用 http 发送请求,获取数据

export const getFoodData = async ()=>{
  const res = http.createHttp()
  try {
    const result = await res.request('https://zhousg.atomgit.net/harmonyos-next/takeaway.json')
    return  JSON.parse(result.result as string)as  Category[]
  }
  catch (error){//出现问题执行:
    promptAction.showToast({message:error.message})
    return Promise.reject(error)//逻辑请求已经出错,不能再继续了
  }
  finally {
    res.destroy()//销毁这个请求
  }

}

MTAddCut

//夹菜和减菜的组件:
import { FoodItem } from '../models'
import { AddCutEnum, CartStore } from '../utils'

@Component
  struct MTAddCut {
    @Consume
    userCart:FoodItem[]//接收MTIndex的provide传过来的购物车
    fid:number=0//接收MTFoodItem传过来的id来跟购物车判断:在不在购物车里
    item:FoodItem = new FoodItem()//接收MTFoodItem传过来的item
    getCount(){//判断当前的菜品和购物车的关系,只要当前菜品在购物车里,就把<数量>显示
      return this.userCart.find(item => item.id === this.fid)?.count || 0  //去购物车里找有没有和这个id一样的菜品,如果有说明这个菜品在购物车里
    }
    build() {
      Row({ space: 8 }) {
        Row() {
          Image($r('app.media.ic_screenshot_line'))
            .width(10)
            .aspectRatio(1)
        }
        .width(16)
          .aspectRatio(1)
          .justifyContent(FlexAlign.Center)
          .backgroundColor($r("app.color.white"))
          .borderRadius(4)
          .border({ width: 0.5 , color: $r("app.color.main_color")})
          .visibility(this.getCount() ? Visibility.Visible : Visibility.Hidden)//没有就消失,但是会占位
          .onClick(()=>{//减菜
            CartStore.addCutCart(AddCutEnum.CUT,this.item)//把item传给-菜方法
          })
        Text(this.getCount().toString())
          .visibility(this.getCount() ? Visibility.Visible : Visibility.Hidden)//没有就消失,但是会占位

        Row() {
          Image($r('app.media.ic_public_add_filled'))
            .width(10)
            .aspectRatio(1)
        }
        .width(16)
          .aspectRatio(1)
          .justifyContent(FlexAlign.Center)
          .backgroundColor($r("app.color.main_color"))
          .borderRadius(4)
          .onClick(()=>{//+菜
            CartStore.addCutCart(AddCutEnum.ADD,this.item)//把item传给加菜方法
          })
      }
    }
  }
export default MTAddCut

MTBottom

import { FoodItem } from '../models'

@Component
  struct MTBottom {
    @Consume//用来接收MTIndex传过来的showCart
    showCart:boolean
    @Consume//接MTIndex传下来的购物车userCart
    userCart:FoodItem[]

    //总件数方法:
    getTotalCount(){
      return this.userCart.reduce((preValue:number,item:FoodItem)=>{
        return preValue + item.count //0+总数
      },0)
    }
    //算总价格:
    getTotalPrice(){
      return this.userCart.reduce((preValue:number,item:FoodItem)=>{
        return preValue + item.count * item.price //0+总数
      },0)
    }

    build() {
      Row(){
        Row(){
          //小哥图像显示:
          Badge({ value: this.getTotalCount().toString(), position: BadgePosition.Right, style:{badgeSize:18}}){
            Image($r("app.media.ic_public_cart"))
              .width(47)
              .height(69)
              .position({y:-20})
          }
          .margin({left:25,right:10})
            .onClick(()=>{//点击小哥,就把showCart取反,来关闭和打开购物车
              this.showCart=!this.showCart
            })
          //显示总费用:
          Column(){
            Text(){
              //span imagespan
              Span("¥")
                .fontSize(12)
              Span(this.getTotalPrice().toString())
                .fontSize(24)
            }
            .fontColor($r("app.color.white"))
            Text("预估另需配送费¥5元")
              .fontColor($r("app.color.search_font_color"))
              .fontSize(14)
          }
          .alignItems(HorizontalAlign.Start)//column的水平轴对齐方式
            .layoutWeight(1)
          //去结算按钮:
          Text("去结算")
            .height(50)
            .textAlign(TextAlign.Center)//字要居中
            .width(80)
            .backgroundColor($r("app.color.main_color"))
            .borderRadius({topRight:25,bottomRight:20})
        }
        .height(50)
          .backgroundColor($r("app.color.bottom_back"))
          .width('100%')
          .borderRadius(25)
      }
      .padding({
        left:20,
        right:20,
        bottom:20,
      })
        .width('100%')
    }
  }
export default MTBottom

MTCart

import { FoodItem } from '../models'
import { CartStore } from '../utils'
import MTCartItem from './MTCartItem'
//购物车组件:
@Component
  struct MTCart {
    @Consume
    userCart:FoodItem[]//接收MTIndex的provide传过来的购物车
    build() {
      Column(){
        Column(){
          Row(){
            Text("购物车")
              .fontSize(12)
            Text("清空购物车")
              .fontSize(12)
              .fontColor($r("app.color.search_font_color"))
              .onClick(()=>{
                CartStore.clearCart()
              })
          }
          .height(50)
            .width('100%')
            .justifyContent(FlexAlign.SpaceBetween)//两头对其
            .padding({left:15,right:15})
          //生成n个的菜单项:
          List({space:20}){
            ForEach(this.userCart,(item:FoodItem)=>{
              ListItem(){
                MTCartItem({item:item})
              }}, (item:FoodItem) => item.id.toString() )//第三个参数 防止闪烁
          }.padding({left:15,right:15})
        }
        .borderRadius({ topLeft:16,topRight:16})
          .padding({ bottom:100 })
          .width('100%')
          .backgroundColor($r('app.color.white'))
      }
      .width('100%')
        .height('100%')
        .justifyContent(FlexAlign.End)
        .backgroundColor("rgba(0,0,0,0.5)")//透明的背景色 0.5透明度
    }
  }
export default MTCart

MTCartItem

import { FoodItem } from '../models'
import MTAddCut from './MTAddCut'

@Component
  struct MTCartItem {//购物车里商品抽提成的组件   单个
    item:FoodItem= new FoodItem()//接收MTCart的foreach传过来的item进行单个渲染
    build() {
      Row({space:10}){
        Image(this.item.picture)
          .width(60)
          .aspectRatio(1)
          .borderRadius(8)
        Column(){
          Text(this.item.name)
            .fontSize(14)
            .textOverflow({
              overflow:TextOverflow.Ellipsis
            })
          Row(){
            Text(){
              Span("¥")
                .fontSize(10)
              Span(this.item.price.toString())//价格
                .fontSize(20)
                .fontWeight(600)
            }
            .fontColor($r('app.color.font_main_color'))
            MTAddCut({fid:this.item.id,item:this.item})
          }
          .width('100%')
            .justifyContent(FlexAlign.SpaceBetween)
        }
        .layoutWeight(1)
          .alignItems(HorizontalAlign.Start)//column的副轴对齐方式
      }
      .width('100%')
    }
  }
export default MTCartItem

MTFoodItem

import { FoodItem } from '../models'
import MTAddCut from './MTAddCut'

@Component
  struct MTFoodItem {       //菜单栏的商品抽提成的组件  单个
    item :FoodItem = new FoodItem()//接收main的 ForEach传过来的数据并渲染
    build() {
      Row() {
        Image(this.item.picture)
          .width(90)
          .aspectRatio(1)
        Column({ space: 5 }) {
          Text(this.item.name)
            .textOverflow({
              overflow: TextOverflow.Ellipsis,
            })
            .maxLines(2)
            .fontWeight(600)
          Text(this.item.description)
            .textOverflow({
              overflow: TextOverflow.Ellipsis,
            })
            .maxLines(1)
            .fontSize(12)
            .fontColor($r("app.color.food_item_second_color"))
          ForEach(this.item.food_tag_list,(str:string)=>{
            Text(str)
              .fontSize(10)
              .backgroundColor($r("app.color.food_item_label_color"))
              .fontColor($r("app.color.font_main_color"))
              .padding({ top: 2, bottom: 2, right: 5, left: 5 })
              .borderRadius(2)
          })
          Text() {
            Span('月销售'+this.item.month_saled)
            Span(' ')
            Span(this.item.like_ratio_desc)
          }
          .fontSize(12)
            .fontColor($r("app.color.black"))

          Row() {
            Text() {
              Span('¥ ')
                .fontColor($r("app.color.font_main_color"))
                .fontSize(10)
              Span(this.item.price.toString())
                .fontColor($r("app.color.font_main_color"))
                .fontWeight(FontWeight.Bold)
            }
            MTAddCut({ fid:this.item.id ,item:this.item})//把id传给MTAddCut
          }
          .justifyContent(FlexAlign.SpaceBetween)
            .width('100%')
        }
        .layoutWeight(1)
          .alignItems(HorizontalAlign.Start)
          .padding({ left: 10, right: 10 })
      }
      .padding(10)
        .alignItems(VerticalAlign.Top)
    }
  }
export default MTFoodItem

MTMain

import { getFoodData } from '../API'
import { Category, FoodItem } from '../models'
import MTFoodItem from './MTFoodItem'
@Component
  struct MTMain {
    @State
    list:Category[]=[]
    // @State
    // list:string[]=["一人套餐","特色烧烤","杂粮主食"]
    @State
    activeIndex:number = 0//激活项的点菜类型索引
    aboutToAppear(): void {
      this.getData()
    }
    //获取数据的方法:
    async getData(){
      this.list = await getFoodData()//调用网络请求方法,把数据传给list
    }


    build() {
      Row(){

        //左侧分类:
        Column(){
          ForEach(this.list,(item:Category,index:number)=>{
            Text(item.name)
              .height(50)
              .width('100%')
              .fontSize(14)
              .backgroundColor(this.activeIndex === index?$r("app.color.white"):$r("app.color.left_back_color"))//选中的话显示白色,不选中默认色
              .textAlign(TextAlign.Center)
              .onClick(()=>{
                this.activeIndex=index//点到谁就把它的索引给激活项索引
              })
          })
        }
        .height('100%')
          .backgroundColor($r("app.color.left_back_color"))
          .width(90)

        //右侧食物列表:
        List(){
          ForEach( this.list[this.activeIndex]?.foods || [] ,(item:FoodItem)=>{
            ListItem(){
              MTFoodItem({item:item})//把数据传给MTFoodItem去渲染一个一个的菜品
            }
          })
        }
        .height('100%')
          .layoutWeight(1)
          .padding({bottom:120})

      }
      .width('100%')
    }
  }
export default MTMain

MTTop

@Component
  struct MTTop {
    @Builder
    NavItem(active: boolean, title: string, subTitle?: string) {
      Column() {
        Text() {
          Span(title)
          if (subTitle) {
            Span(' ' + subTitle)
              .fontSize(10)
              .fontColor(active ? $r("app.color.black") : $r("app.color.un_select_color"))
          }
        }.layoutWeight(1)
          .fontColor(active ? $r("app.color.black") : $r("app.color.un_select_color"))
          .fontWeight(active ? FontWeight.Bold : FontWeight.Normal)

        Text()
          .height(1)
          .width(20)
          .margin({ left: 6 })
          .backgroundColor(active ? $r("app.color.select_border_color") : 'transparent')
      }
      .width(73)
        .alignItems(HorizontalAlign.Start)
        .padding({ top: 3 })
    }
    build() {
      Row() {
        this.NavItem(true, '点菜')
        this.NavItem(false, '评价', '1796')
        this.NavItem(false, '商家')
        Row() {
          Image($r('app.media.ic_public_search'))
            .width(14)
            .aspectRatio(1)
            .fillColor($r("app.color.search_font_color"))
          Text('请输入菜品名称')
            .fontSize(12)
            .fontColor($r("app.color.search_back_color"))
        }
        .backgroundColor($r("app.color.search_back_color"))
          .height(25)
          .borderRadius(13)
          .padding({ left: 5, right: 5 })
          .layoutWeight(1)

      }
      .padding({ left: 15, right: 15 })
        .height(40)
        .border({ width: { bottom: 0.5 }, color: $r("app.color.top_border_color") })
    }
  }

export default MTTop

model/index

//数据类型:


//1.菜品类型:
export class FoodItem {
  id: number = 0
  name: string = ""
  like_ratio_desc: string = ""//好评率
  food_tag_list: string[] = []//点评网友推荐
  price: number = 0//价格
  picture: string = ""
  description: string = ""//描述
  tag: string = ""
  month_saled: number = 0//月销量
  count: number = 0
}

//2.分类类型:
export class Category {
  tag: string = ""
  name: string =""
  foods: FoodItem[] = []
}

utils/index

import { it } from '@ohos/hypium'
import { FoodItem } from '../models'
//控制购物车的读取
//单例 只有一个实例
PersistentStorage.persistProp("user_cart",[]) //声明了一个持久化的数据叫user_cart,初始值为空数组

export class CartStore {
  //1.获取购物车方法:
  static getCarts (): FoodItem[] {
    return  AppStorage.get("user_cart") || [] as FoodItem[] //如果拿不到,给上一个空数组
  }

  //2.加菜和减菜的方法:
  static addCutCart(type:AddCutEnum ,item:FoodItem){
    const list = CartStore.getCarts()//拿到持久化购物车给list
    const findFood = list.find(obj => obj.id === item.id)
    if (type===AddCutEnum.ADD) {
      //加菜:这个菜在购物车存在与否?
      if (findFood) {//意味着你点击的菜购物车里已经有了
        findFood.count++
      }
      else {//你的购物车里没有这个菜:给购物车加入这个菜,数量为1:
        list.unshift(item)//把这个菜加到头部
        item.count=1
      }
    }
    else if (type===AddCutEnum.CUT){
      //减菜:
      if (findFood && findFood.count>0 ) {//意味着你点击的菜 购物车里已经有了
        findFood.count--
        if (findFood.count === 0) {//减完了,得把这个菜删除购物车
          let index = list.findIndex(obj=>obj.id === findFood.id)
          list.splice(index,1)
        }
      }
    }
    //最后要把这个list数据更新到持久化
    AppStorage.set("user_cart",[...list])//写入持久化,把list进行了一次拷贝
    //写入的时候,如果发现对象的地址一致,就不写入
    //第二种方法:事件总线
    //线程内事件总线,线程间事件总线,进程间事件总线
    getContext().eventHub.emit("change_cart")//触发更新购物车事件
  }

  //3.清空购物车方法:
  static clearCart(){
    AppStorage.set("user_cart",[])//把"user_cart"设成空数组
    getContext().eventHub.emit("change_cart")//触发更新购物车事件
  }
}

//写成枚举给addCutCart用
export enum AddCutEnum{
  ADD,
  CUT
}

效果:

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值