ToB企服应用市场:ToB评测及商务社交产业平台
标题:
基于鸿蒙HarmonyOS_NEXT的美团点餐案例
[打印本页]
作者:
用户国营
时间:
2024-12-25 03:07
标题:
基于鸿蒙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
}
复制代码
效果:
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
欢迎光临 ToB企服应用市场:ToB评测及商务社交产业平台 (https://dis.qidao123.com/)
Powered by Discuz! X3.4