0. 整体结构
整体划分3部分。店铺部分,购物车部分,金额统计部分。使用 Stack 把3部分堆叠
0.1 整体页面 Index.ets
修改 Index.ets ,使用堆叠布局,并居底部对齐
import { ElShop } from '../components/ElShop'
import { ElShoppingCart } from '../components/ElShoppingCart'
import { ElSubtotal } from '../components/ElSubtotal'
@Entry
@Component
struct Index {
build() {
Column() {
Stack({ alignContent: Alignment.Bottom }) {
ElShop()
ElShoppingCart()
ElSubtotal()
}
}
.width("100%")
.height("100%")
}
}
0.2 创建 ElShop 组件
创建 ElShop 店铺部分组件
@Component
export struct ElShop {
build() {
Column() {
}
.width("100%")
.height("100%")
.backgroundColor(Color.Red)
}
}
0.3 创建 ElShoppingCart 组件
创建购物车部分组件
@Component
export struct ElShoppingCart {
build() {
Column() {
}
.width("100%")
.height(300)
.backgroundColor(Color.Green)
}
}
0.4 创建 ElSubtotal 组件
创建金额统计部分组件
@Component
export struct ElSubtotal {
build() {
Column() {
}
.width("100%")
.height(80)
.backgroundColor(Color.Blue)
}
}
0.5 创建 model
创建 models 文件夹,创建 Product.ets 文件
export class Product {
id: number = 0
name: string = ""
positive_reviews: string = ""
food_label_list: string[] = []
price: number = 0
picture: string = ""
description: string = ""
tag: string = ""
monthly_sales: number = 0
}
export class SelectedProduct extends Product {
count: number = 0
}
export class Category {
id: number = 0
name: string = ""
foods: Product[] = []
}
1. 店铺部分
1.1 修改 ElShop 组件
划分 header,tabbar,body 三部分
Column [ ElShopHeader,ElShopTabbar,ElShopBody ]
import { ElShopHeader } from './ElShopHeader'
import { ElShopTabbar } from './ElShopTabbar'
import { ElShopBody } from './ElShopBody'
@Component
export struct ElShop {
build() {
Column() {
ElShopHeader()
ElShopTabbar()
ElShopBody()
}
.width("100%")
.height("100%")
.backgroundColor(Color.White)
}
}
1.2 创建 ElShopHeader 组件
Row [ 返回图标,(搜索图标,文字),消息图标,喜欢图标,加号图标 ]
@Component
export struct ElShopHeader {
build() {
Row() {
Image($r("app.media.left"))
.width(20)
.height(20)
.fillColor("#191919")
Row() {
Image($r('app.media.search'))
.width(14)
.aspectRatio(1)
.fillColor('#555')
.margin({ right: 5 })
Text('搜一搜')
.fontSize(12)
.fontColor('#555')
}
.width(150)
.height(30)
.backgroundColor('#eee')
.borderRadius(15)
.padding({ left: 5, right: 5 })
Image($r('app.media.message'))
.width(20)
.fillColor("#191919")
Image($r('app.media.favor'))
.width(20)
.fillColor("#191919")
Image($r("app.media.add"))
.width(20)
.fillColor("#191919")
}
.width('100%')
.height(60)
.backgroundColor('#fbfbfb')
.padding(10)
.justifyContent(FlexAlign.SpaceAround)
}
}
1.3 创建 ElShopTabbar 组件
Row [ 点餐,评价,商家 ]
每一个tab用 @Builder 函数创建
@Component
export struct ElShopTabbar {
@Builder
TabItem(active: boolean, title: string, subtitle?: string) {
Column() {
Text() {
Span(title)
if (subtitle) {
Span(' ' + subtitle)
.fontSize(10)
.fontColor(active ? '#000' : '#666')
}
}
.layoutWeight(1)
.fontColor(active ? '#000' : '#666')
.fontWeight(active ? FontWeight.Bold : FontWeight.Normal)
Column()
.width(20)
.height(3)
.borderRadius(5)
.backgroundColor(active ? '#02B6FD' : 'transparent')
}
.alignItems(HorizontalAlign.Center)
.padding({ left: 15, right: 15 })
}
build() {
Row() {
this.TabItem(true, '点餐')
this.TabItem(false, '评价', '196')
this.TabItem(false, '商家')
}
.width("100%")
.height(40)
.justifyContent(FlexAlign.Start)
.backgroundColor('#fbfbfb')
}
}
1.4 创建 ElShopBody 组件
这里分为左边分类列表,右边商品列表
Row [ 分类列表,商品列表 ]
import { ElShopCategory } from './ElShopCategory'
import { ElShopProduct } from './ElShopProduct'
@Component
export struct ElShopBody {
build() {
Row() {
ElShopCategory()
ElShopProduct()
}
.width('100%')
.layoutWeight(1)
.alignItems(VerticalAlign.Top)
}
}
1.5 创建 ElShopCategory 组件
分类列表,每一项是分类文字
import { Category } from '../models/Product'
@Component
export struct ElShopCategory {
@State categoryList: Category[] = [
{ id: 1, name: '必点招牌', foods: [] },
{ id: 2, name: '超值套餐', foods: [] },
{ id: 3, name: '杂粮主食', foods: [] },
]
@State categoryIndex: number = 0
build() {
Column() {
ForEach(this.categoryList, (item: Category, index: number) => {
Text(item.name)
.width('100%')
.height(40)
.textAlign(TextAlign.Center)
.fontSize(12)
.backgroundColor(this.categoryIndex === index ? '#fff' : 'transparent')
.onClick(() => {
this.categoryIndex = index
})
})
}
.width(90)
.height('100%')
.backgroundColor('#eee')
}
}
1.6 创建 ElShopProduct 组件
商品列表,每一项是商品项
import { ElProductItem } from './ElProductItem'
@Component
export struct ElShopProduct {
build() {
List({ space: 20 }) {
ForEach([1, 2, 3, 4, 5, 6, 7, 8, 9], () => {
ListItem() {
ElProductItem()
}
})
}
.layoutWeight(1)
.backgroundColor('#fff')
.padding({ left: 10, right: 10 })
}
}
1.7 创建 ElProductItem 组件
商品的每一项
Row [ 图片,内容 ]
@Component
export struct ElProductItem {
build() {
Row() {
Image('https://gimg2.baidu.com/image_search/src=http%3A%2F%2Fsafe-img.xhscdn.com%2Fbw1%2F67ba10b0-b4a0-4dd7-b343-31830e01b616%3FimageView2%2F2%2Fw%2F1080%2Fformat%2Fjpg&refer=http%3A%2F%2Fsafe-img.xhscdn.com&app=2002&size=f9999,10000&q=a80&n=0&g=0n&fmt=auto?sec=1711612969&t=b2102c0d151f8225ba531caadf26dd6f')
.width(60)
.aspectRatio(1)
.borderRadius(8)
Column({ space: 5 }) {
Text('猪脚+肉卷+鸡蛋')
.fontSize(14)
.textOverflow({
overflow: TextOverflow.Ellipsis
})
.maxLines(1)
Text('用料:猪脚,肉卷,鸡蛋')
.fontSize(12)
.fontColor('#999')
.textOverflow({
overflow: TextOverflow.Ellipsis
})
.maxLines(1)
Row() {
Text() {
Span('¥ ')
.fontColor('#FF4B33')
.fontSize(10)
Span('38.65')
.fontColor('#FF4B33')
.fontWeight(FontWeight.Bold)
}
// 商品数量操作
}
.justifyContent(FlexAlign.SpaceBetween)
.width('100%')
}
.layoutWeight(1)
.alignItems(HorizontalAlign.Start)
.justifyContent(FlexAlign.SpaceBetween)
.padding({ left: 10, right: 10 })
.height(60)
}
.alignItems(VerticalAlign.Top)
}
}
2. 金额统计部分
2.1 修改 ElSubtotal 组件
Row [ 购物车图标,金额文字,结算按钮 ]
@Component
export struct ElSubtotal {
build() {
Row() {
Badge({
count: 1,
position: BadgePosition.RightTop,
style: { badgeSize: 20 }
}) {
Image($r("app.media.shopping_cart_icon"))
}
.width(50)
.height(50)
.margin({ right: 10 })
Column() {
Text() {
Span('¥')
.fontSize(14)
Span('0')
.fontSize(24)
}
Text('另需配送费约 ¥3.3')
.fontSize(12)
.fontColor('#999')
}
.layoutWeight(1)
.alignItems(HorizontalAlign.Start)
Button('去结算')
.fontSize(18)
.backgroundColor('#02B6FD')
.padding({ left: 30, right: 30 })
}
.width('100%')
.height(80)
.padding(10)
.alignItems(VerticalAlign.Center)
.backgroundColor(Color.White)
.border({
color: "#f5f5f5",
width: {
top: "1"
}
})
}
}
3. 购物车部分
给购物车内容的外层嵌套一个透明的遮罩
外层遮罩 Column [ Colunm( 标题,已选商品列表 ) ]
3.1 修改 ElShoppingCart 组件
import { ElProductItem } from './ElProductItem'
@Component
export struct ElShoppingCart {
build() {
Column() {
Column() {
Row() {
Text('已选商品')
.fontSize(13)
.fontWeight(600)
Row() {
Image($r("app.media.delete"))
.height(14)
.fillColor('#999')
.margin({ right: 5 })
Text('清空')
.fontSize(13)
.fontColor('#999')
}
}
.width('100%')
.justifyContent(FlexAlign.SpaceBetween)
.padding(15)
List({ space: 20 }) {
ForEach([1, 2, 3, 4], () => {
ListItem() {
ElProductItem()
}
})
}
.divider({
strokeWidth: 1,
color: '#ddd'
})
.padding({ left: 15, right: 15, bottom: 100 })
}
.backgroundColor('#fff')
.borderRadius({
topLeft: 16,
topRight: 16
})
}
.width('100%')
.height('100%')
.justifyContent(FlexAlign.End)
.backgroundColor('rgba(0,0,0,0.5)')
}
}
3.2 修改购物车显示隐藏
当点击底部统计部分才显示购物车部分
修改 Index.ets ,添加 showShoppingCart 属性
@Entry
@Component
struct Index {
@State showShoppingCart: boolean = false
build() {
Column() {
Stack({ alignContent: Alignment.Bottom }) {
ElShop()
if (this.showShoppingCart) {
ElShoppingCart()
}
ElSubtotal({ showShoppingCart: $showShoppingCart })
}
}
.width("100%")
.height("100%")
}
}
修改 ElSubtotal 金额统计部分组件,接受 showShoppingCart 属性
@Link showShoppingCart: boolean
修改 ElSubtotal 组件,添加点击事件修改 showShoppingCart 值
.onClick(() => {
this.showShoppingCart = !this.showShoppingCart
})
4. 渲染商品数据
4.1 安装 live-server
使用 npm 全局安装 live-server 包
npm i live-server -g
在 elshop.json 文件夹启动 live-server
live-server
4.2 安装 axios
在项目中安装 axios
ohpm install @ohos/axios
4.3 获取 elshop.json 数据
修改 Index.ets,获取json数据
@Entry
@Component
struct Index {
@State showShoppingCart: boolean = false
@Provide categoryList: Category[] = []
@Provide categoryIndex: number = 0
aboutToAppear() {
this.getData()
}
async getData() {
const res = await axios.get("http://127.0.0.1:8080/elshop.json")
const category = res.data.category.map(item => {
const foods = item.foods.map(food => {
return { ...food, count: 0 }
})
return { ...item, foods }
})
this.categoryList = category
}
}
4.4 修改 ElShopCategory 组件
修改从祖先组件获取分类数据
@Component
export struct ElShopCategory {
@Consume categoryIndex: number
@Consume categoryList: Category[]
}
4.5 修改 ElShopProduct 组件
修改从祖先组件获取分类数据,循环分类下的商品,并把 product 传给 ElProductItem 组件
import { Category, SelectedProduct } from '../models/Product'
import { ElProductItem } from './ElProductItem'
@Component
export struct ElShopProduct {
@Consume categoryIndex: number
@Consume categoryList: Category[]
build() {
List({ space: 20 }) {
ForEach(this.categoryList[this.categoryIndex]?.foods ?? [], (product: SelectedProduct) => {
ListItem() {
ElProductItem({ product })
}
})
}
.layoutWeight(1)
.backgroundColor('#fff')
.padding({ left: 10, right: 10 })
}
}
4.6 修改 ElProductItem 组件
修改 ElProductItem 组件,接收 product 数据
import { SelectedProduct } from '../models/Product'
@Component
export struct ElProductItem {
product: SelectedProduct
build() {
Row() {
Image(this.product.picture)
.width(60)
.aspectRatio(1)
.borderRadius(8)
Column({ space: 5 }) {
Text(this.product.name)
.fontSize(14)
.textOverflow({
overflow: TextOverflow.Ellipsis
})
.maxLines(1)
Text(this.product.description)
.fontSize(12)
.fontColor('#999')
.textOverflow({
overflow: TextOverflow.Ellipsis
})
.maxLines(1)
Row() {
Text() {
Span('¥ ')
.fontColor('#FF4B33')
.fontSize(10)
Span(this.product.price.toString())
.fontColor('#FF4B33')
.fontWeight(FontWeight.Bold)
}
// 商品数量操作
}
.justifyContent(FlexAlign.SpaceBetween)
.width('100%')
}
.layoutWeight(1)
.alignItems(HorizontalAlign.Start)
.justifyContent(FlexAlign.SpaceBetween)
.padding({ left: 10, right: 10 })
.height(60)
}
.alignItems(VerticalAlign.Top)
}
}
5. 商品数量操作
5.1 创建 utils/productUtil.ets 文件
为了持久化保存已选择的商品,把选中的商品保存到 AppStorage 中
- 声明保存到 AppStoreage 的 key
- 添加已选的商品 addProduct
- 删除已选的商品 removeProduct
- 清空已选的商品 cleartAllProduct
import { Product, SelectedProduct } from '../models/Product'
export const SHOPPING_CART_KEY = "SHOPPING_CART"
// 添加商品
export const addProduct = (product: Product) => {
const products = JSON.parse(AppStorage.Get<string>(SHOPPING_CART_KEY) || '[]') as SelectedProduct[]
const selectedProduct = products.find(item => item.id === product.id)
if (selectedProduct) {
selectedProduct.count++
} else {
products.push({ ...product, count: 1 })
}
AppStorage.Set<string>(SHOPPING_CART_KEY, JSON.stringify(products))
}
// 删除商品
export const removeProduct = (id: number) => {
const products = JSON.parse(AppStorage.Get<string>(SHOPPING_CART_KEY) || '[]') as SelectedProduct[]
const index = products.findIndex(item => item.id === id)
const selectedProduct = products[index]
if (selectedProduct && selectedProduct.count > 0) {
selectedProduct.count--
if (selectedProduct.count <= 0) {
products.splice(index, 1)
}
}
AppStorage.Set<string>(SHOPPING_CART_KEY, JSON.stringify(products))
}
// 清空商品
export const clearAllProduct = () => {
AppStorage.Set<string>(SHOPPING_CART_KEY, "[]")
}
5.2 修改 Index.ets 文件
在 Index.ets 页面初始化持久化的数据
import { SHOPPING_CART_KEY } from '../utils/productUtil'
PersistentStorage.PersistProp(SHOPPING_CART_KEY,"[]")
添加持久化的json数据属性,并监听更新变化
@StorageLink(SHOPPING_CART_KEY)
@Watch("update")
productListJson: string = "[]"
@Provide selectedProductList: SelectedProduct[] = []
update() {
this.selectedProductList = JSON.parse(this.productListJson)
}
5.3 修改 ElShoppingCart 组件
接收已选中商品数据 selectedProductList
export struct ElShoppingCart {
@Consume selectedProductList: SelectedProduct[]
}
并修改列表渲染,把 product 传给 ElProductItem 组件
List({ space: 20 }) {
ForEach(this.selectedProductList, (product: SelectedProduct) => {
ListItem() {
ElProductItem({ product })
}
})
}
给清空按钮添加事件
.onClick(() => {
clearAllProduct()
})
5.4 创建 ElProductCount 商品数量组件
import { SelectedProduct } from '../models/Product'
@Component
export struct ElProductCount {
product: SelectedProduct
build() {
Row({ space: 8 }) {
Image($r('app.media.minus_circle'))
.width(14)
.aspectRatio(1)
.fillColor("#02B6FD")
Text('0').fontSize(14)
Image($r('app.media.plus_circle'))
.width(14)
.aspectRatio(1)
.fillColor("#02B6FD")
}
}
}
5.5 修改 ElProductItem 组件
在金额旁边添加数量组件
ElProductCount({ product:this.product })
5.6 修改 ElProductCount 组件
- 接收 product 数据
- 接收 selectedProductList 数据
- 获取该商品的数量
- 给图标绑定添加商品,删除商品的事件
import { SelectedProduct } from '../models/Product'
import { addProduct, removeProduct } from '../utils/productUtil'
@Component
export struct ElProductCount {
@Consume selectedProductList: SelectedProduct[]
product: SelectedProduct
getCount() {
const selectedProduct = this.selectedProductList.find(item => item.id === this.product.id)
return selectedProduct?.count || 0
}
build() {
Row({ space: 8 }) {
Image($r('app.media.minus_circle'))
.width(14)
.aspectRatio(1)
.fillColor("#02B6FD")
.onClick(() => {
removeProduct(this.product.id)
})
Text(`${this.getCount()}`).fontSize(14)
Image($r('app.media.plus_circle'))
.width(14)
.aspectRatio(1)
.fillColor("#02B6FD")
.onClick(() => {
addProduct(this.product)
})
}
}
}
5.7 修改 ElSubtotal 组件
- 接收已选中商品 selectedProductList
- 添加商品总数据方法
- 添加商品总金额方法
@Component
export struct ElSubtotal {
@Link showShoppingCart: boolean
@Consume selectedProductList: SelectedProduct[]
getTotalCount() {
return this.selectedProductList.reduce((count, item) => {
return count + item.count
}, 0)
}
getTotalPrice() {
return this.selectedProductList.reduce((price, item) => {
return price + (item.count * item.price * 100)
}, 0) / 100
}
}
6. 文件
elshop.json文件
https://download.****.net/download/d312697510/89141677
icon图标
https://download.****.net/download/d312697510/89141683
git仓库地址
https://github.com/webdq/ElShop