商品页 | 购物车 |
---|---|
1import { Cart } from '../components/Cart'
2import { Footer } from '../components/Footer'
3import { MenuWrapper } from '../components/MenuWrapper'
4import { Nav } from '../components/Nav'
5
6@Entry
7@Component
8struct Index {
9
10 @State
11 showCart: boolean = false
12
13 build() {
14 Stack({ alignContent: Alignment.Bottom }) {
15 Column() {
16 Nav()
17 MenuWrapper()
18 }
19 .width('100%')
20 .height('100%')
21 if (this.showCart) {
22 Cart()
23 }
24 Footer({ showCart: $showCart })
25 }
26 }
27}
1@Component
2export struct Footer {
3 @Link
4 showCart: boolean
5
6 build() {
7 Row() {
8 Row() {
9 Badge({
10 value: '0',
11 position: BadgePosition.Right,
12 style: { badgeSize: 18 }
13 }) {
14 Image('https://zqran.gitee.io/images/waimai/cart-2.png')
15 .width(47)
16 .height(69)
17 .position({ y: -19 })
18 }
19 .width(50)
20 .height(50)
21 .margin({ left: 25, right: 10 })
22 .onClick(() => {
23 this.showCart = !this.showCart
24 })
25
26 Column() {
27 Text() {
28 Span('¥')
29 .fontColor('#fff')
30 .fontSize(12)
31 Span('0.00')
32 .fontColor('#fff')
33 .fontSize(24)
34 }
35
36 Text('预估另需配送费 ¥5')
37 .fontSize(12)
38 .fontColor('#999')
39 }
40 .layoutWeight(1)
41 .alignItems(HorizontalAlign.Start)
42
43 Text('去结算')
44 .backgroundColor('#f8c74e')
45 .alignSelf(ItemAlign.Stretch)
46 .padding(15)
47 .borderRadius({
48 topRight: 25,
49 bottomRight: 25
50 })
51 }
52 .height(50)
53 .layoutWeight(1)
54 .backgroundColor('#222426')
55 .borderRadius(25)
56 }
57 .padding(15)
58 .height(80)
59 }
60}
1@Component
2export struct Nav {
3 @Builder
4 NavItem(active: boolean, title: string, subTitle?: string) {
5 Column() {
6 Text() {
7 Span(title)
8 if (subTitle) {
9 Span(' ' + subTitle)
10 .fontSize(10)
11 .fontColor(active ? '#000' : '#666')
12 }
13 }.layoutWeight(1)
14 .fontColor(active ? '#000' : '#666')
15 .fontWeight(active ? FontWeight.Bold : FontWeight.Normal)
16
17 Text()
18 .height(1)
19 .width(20)
20 .margin({ left: 6 })
21 .backgroundColor(active ? '#fa0' : 'transparent')
22 }
23 .width(73)
24 .alignItems(HorizontalAlign.Start)
25 .padding({ top: 3 })
26 }
27
28 build() {
29 Row() {
30 this.NavItem(true, '点菜')
31 this.NavItem(false, '评价', '1796')
32 this.NavItem(false, '商家')
33
34 Row() {
35 Image($r('app.media.ic_public_input_search'))
36 .width(14)
37 .aspectRatio(1)
38 .fillColor('#999')
39 Text('请输入菜品名称')
40 .fontSize(12)
41 .fontColor('#999')
42 }
43 .backgroundColor('#eee')
44 .height(25)
45 .borderRadius(13)
46 .padding({ left: 5, right: 5 })
47 .layoutWeight(1)
48 }
49 .padding({ left: 15, right: 15 })
50 .height(40)
51 .border({ width: { bottom: 0.5 }, color: '#e4e4e4' })
52 }
53}
1import { MenuWrapperItem } from './MenuWrapperItem'
2
3@Component
4export struct MenuWrapper {
5 list: string[] = ['一人套餐', '特色烧烤', '杂粮主食']
6 @State
7 activeIndex: number = 0
8
9 build() {
10 Row() {
11 Column() {
12 ForEach(this.list, (item: string, index: number) => {
13 Text(item)
14 .height(50)
15 .width('100%')
16 .textAlign(TextAlign.Center)
17 .fontSize(14)
18 .backgroundColor(this.activeIndex === index ? '#fff' : '#f5f5f5')
19 .onClick(() => {
20 this.activeIndex = index
21 })
22 })
23 }
24 .width(90)
25
26 List() {
27 ListItem(){
28 MenuWrapperItem()
29 }
30 }
31 .layoutWeight(1)
32 .height('100%')
33 .backgroundColor('#fff')
34 }
35 .layoutWeight(1)
36 .alignItems(VerticalAlign.Top)
37 .backgroundColor('#f5f5f5')
38 }
39}
1import { CalcBtn } from './CalcBtn'
2
3@Component
4export struct MenuWrapperItem {
5 build() {
6 Row() {
7 Image('https://zqran.gitee.io/images/waimai/8078956697.jpg')
8 .width(90)
9 .aspectRatio(1)
10 Column({ space: 5 }) {
11 Text('小份酸汤莜面鱼鱼+肉夹馍套餐')
12 .textOverflow({
13 overflow: TextOverflow.Ellipsis,
14 })
15 .maxLines(2)
16 .fontWeight(600)
17 Text('酸汤莜面鱼鱼,主料:酸汤、莜面 肉夹馍,主料:白皮饼、猪肉')
18 .textOverflow({
19 overflow: TextOverflow.Ellipsis,
20 })
21 .maxLines(1)
22 .fontSize(12)
23 .fontColor('#333')
24 Text('点评网友推荐')
25 .fontSize(10)
26 .backgroundColor('#fff5e2')
27 .fontColor('#ff8000')
28 .padding({ top: 2, bottom: 2, right: 5, left: 5 })
29 .borderRadius(2)
30 Text() {
31 Span('月销售40')
32 Span(' ')
33 Span('好评度100%')
34 }
35 .fontSize(12)
36 .fontColor('#999')
37
38 Row() {
39 Text() {
40 Span('¥ ')
41 .fontColor('#ff8000')
42 .fontSize(10)
43 Span('34.23')
44 .fontColor('#ff8000')
45 .fontWeight(FontWeight.Bold)
46 }
47
48 CalcBtn({ icon: $r('app.media.ic_public_add_filled') })
49 }
50 .justifyContent(FlexAlign.SpaceBetween)
51 .width('100%')
52 }
53 .layoutWeight(1)
54 .alignItems(HorizontalAlign.Start)
55 .padding({ left: 10, right: 10 })
56 }
57 .padding(10)
58 .alignItems(VerticalAlign.Top)
59 }
60}
1)定义 model 数据模型
1export class FoodItem {
2 id: number
3 name: string
4 like_ratio_desc: string
5 food_tag_list: string[]
6 price: number
7 picture: string
8 description: string
9 tag: string
10 month_saled: number
11}
12
13export class Category {
14 tag: string
15 name: string
16 foods: FoodItem[]
17}
2)使用 http
发送请求,获取数据
1import http from '@ohos.net.http'
2import { Cart } from '../components/Cart'
3import { Footer } from '../components/Footer'
4import { MenuWrapper } from '../components/MenuWrapper'
5import { Nav } from '../components/Nav'
6import { Category } from '../models'
7
8const req = http.createHttp()
9
10@Entry
11@Component
12struct Index {
13 @State
14 showCart: boolean = false
15
16 @State
17 categoryList: Category[] = []
18
19 aboutToAppear() {
20 req.request('http://172.16.39.26:3000/takeaway')
21 .then(res => {
22 const data = JSON.parse(res.result as string) as Category[]
23 this.categoryList = data
24 })
25 .catch(err => {
26 console.error('MEITU', err.message)
27 })
28 }
29
30 build() {
31 Stack({ alignContent: Alignment.Bottom }) {
32 Column() {
33 Nav()
34 MenuWrapper({ categoryList: $categoryList })
35 }
36 .width('100%')
37 .height('100%')
38
39 if (this.showCart) {
40 Cart()
41 }
42 Footer({ showCart: $showCart })
43 }
44 }
45}
3)传入列表数据给,商品菜单组件,进行渲染
1import { Category, FoodItem } from '../models'
2import { MenuWrapperItem } from './MenuWrapperItem'
3
4@Component
5export struct MenuWrapper {
6 @Link
7 categoryList: Category[]
8 @State
9 activeIndex: number = 0
10
11 build() {
12 Row() {
13 Column() {
14 ForEach(this.categoryList, (item: Category, index: number) => {
15 Text(item.name)
16 .height(50)
17 .width('100%')
18 .textAlign(TextAlign.Center)
19 .fontSize(14)
20 .backgroundColor(this.activeIndex === index ? '#fff' : '#f5f5f5')
21 .onClick(() => {
22 this.activeIndex = index
23 })
24 })
25 }
26 .width(90)
27
28 List() {
29 ForEach(this.categoryList[this.activeIndex]?.foods, (item: FoodItem) => {
30 ListItem() {
31 MenuWrapperItem({ food: item })
32 }
33 })
34 }
35 .layoutWeight(1)
36 .height('100%')
37 .backgroundColor('#fff')
38 }
39 .layoutWeight(1)
40 .alignItems(VerticalAlign.Top)
41 .backgroundColor('#f5f5f5')
42 }
43}
1import { FoodItem } from '../models'
2import { CalcBtn } from './CalcBtn'
3
4@Component
5export struct MenuWrapperItem {
6
7 @ObjectLink
8 food: FoodItem
9
10 build() {
11 Row() {
12 Image(this.food.picture)
13 .width(90)
14 .aspectRatio(1)
15 Column({ space: 5 }) {
16 Text(this.food.name)
17 .textOverflow({
18 overflow: TextOverflow.Ellipsis,
19 })
20 .maxLines(2)
21 .fontWeight(600)
22 Text(this.food.description)
23 .textOverflow({
24 overflow: TextOverflow.Ellipsis,
25 })
26 .maxLines(1)
27 .fontSize(12)
28 .fontColor('#333')
29 ForEach(this.food.food_tag_list, (tag) => {
30 Text(tag)
31 .fontSize(10)
32 .backgroundColor('#fff5e2')
33 .fontColor('#ff8000')
34 .padding({ top: 2, bottom: 2, right: 5, left: 5 })
35 .borderRadius(2)
36 })
37 Text() {
38 Span('月销售'+this.food.month_saled)
39 Span(' ')
40 Span(this.food.like_ratio_desc)
41 }
42 .fontSize(12)
43 .fontColor('#999')
44
45 Row() {
46 Text() {
47 Span('¥ ')
48 .fontColor('#ff8000')
49 .fontSize(10)
50 Span(this.food.price.toFixed(2))
51 .fontColor('#ff8000')
52 .fontWeight(FontWeight.Bold)
53 }
54
55 CalcBtn({ icon: $r('app.media.ic_public_add_filled') })
56 }
57 .justifyContent(FlexAlign.SpaceBetween)
58 .width('100%')
59 }
60 .layoutWeight(1)
61 .alignItems(HorizontalAlign.Start)
62 .padding({ left: 10, right: 10 })
63 }
64 .padding(10)
65 .alignItems(VerticalAlign.Top)
66 }
67}
1@Component
2export struct CalcBtn {
3
4 icon: Resource
5
6 plain?: boolean
7
8 build() {
9 Row() {
10 Image(this.icon)
11 .width(10)
12 .aspectRatio(1)
13 }
14 .width(16)
15 .aspectRatio(1)
16 .backgroundColor(this.plain ? '#fff' : '#f8c74e')
17 .border(this.plain ? { width: 0.5 , color: '#f8c74e'}: {})
18 .borderRadius(4)
19 .justifyContent(FlexAlign.Center)
20 }
21}
1import { CartItem } from './CartItem'
2@Component
3export struct Cart {
4 build() {
5 Column(){
6 Column(){
7 Row(){
8 Text('购物车')
9 .fontSize(14)
10 Text('清空购物车')
11 .fontSize(14)
12 .fontColor('#999')
13 }
14 .width('100%')
15 .height(40)
16 .justifyContent(FlexAlign.SpaceBetween)
17 .padding({ left: 15, right: 15 })
18 .border({ width: { bottom: 0.5 }, color: '#e4e4e4' })
19 // 购物车列表
20 List(){
21 ListItem(){
22 CartItem()
23 }
24 ListItem(){
25 CartItem()
26 }
27 ListItem(){
28 CartItem()
29 }
30 }
31 .divider({
32 strokeWidth: 0.5,
33 color: '#e4e4e4'
34 })
35 .padding({ left: 15, right: 15 })
36 }
37 .width('100%')
38 .padding({ bottom: 100 })
39 .backgroundColor('#fff')
40 .borderRadius({
41 topLeft: 16,
42 topRight: 16
43 })
44 }
45 .height('100%')
46 .width('100%')
47 .backgroundColor('rgba(0,0,0,0.5)')
48 .justifyContent(FlexAlign.End)
49 }
50}
1import { CalcBtn } from './CalcBtn'
2@Component
3export struct CartItem {
4 build() {
5 Row(){
6 Image('https://zqran.gitee.io/images/waimai/7384994864.jpg')
7 .width(60)
8 .aspectRatio(1)
9 Column({ space: 10 }){
10 Text('小份酸汤莜面鱼鱼+肉夹馍套餐')
11 .textOverflow({
12 overflow: TextOverflow.Ellipsis
13 })
14 .maxLines(2)
15 Row(){
16 Text(){
17 Span('¥ ')
18 .fontSize(10)
19 .fontColor('#ff8000')
20 Span('23.23')
21 .fontWeight(600)
22 .fontColor('#ff8000')
23 }
24
25 Row({ space: 10 }){
26 CalcBtn({ icon: $r('app.media.ic_screenshot_line'), plain: true })
27 Text('0')
28 .fontSize(14)
29 CalcBtn({ icon: $r('app.media.ic_public_add_filled') })
30 }
31 }
32 .width('100%')
33 .justifyContent(FlexAlign.SpaceBetween)
34 }
35 .layoutWeight(1)
36 .alignItems(HorizontalAlign.Start)
37 .padding({ left: 10 })
38 }
39 .padding({ top: 15, bottom: 15 })
40 .alignItems(VerticalAlign.Top)
41 }
42}
1)购物车数据模型
1export class CartItemModel {
2 id: number
3 name: string
4 price: number
5 picture: string
6 count: number
7}
2)购物车添加逻辑
1import { CartItemModel, FoodItem } from '../models'
2
3export const CART_KEY = 'CART_KEY'
4
5export const getCart = () => {
6 return JSON.parse(AppStorage.Get(CART_KEY) || '[]') as CartItemModel[]
7}
8
9export const addCart = (food: FoodItem) => {
10 const cart = getCart()
11 const f = cart.find(f => f.id === food.id)
12 if (f) {
13 f.count++
14 } else {
15 const { id, price, picture, name } = food
16 cart.unshift({
17 id,
18 price,
19 picture,
20 name,
21 count: 1
22 })
23 }
24 AppStorage.Set(CART_KEY, JSON.stringify(cart))
25}
3)购物车状态持久化
1import { CART_KEY } from '../utils'
2
3
4PersistentStorage.PersistProp(CART_KEY, '[]')
4)监听购物车数据变化,设置购物车状态,底部组件显示数量和总价
1@StorageProp(CART_KEY)
2 @Watch('onUpdateCart')
3 cartJson: string = '[]'
4 @State
5 cart: CartItem[] = JSON.parse(this.cartJson)
6 onUpdateCart () {
7 this.cart = JSON.parse(this.cartJson)
8 }
1Footer({ showCart: $showCart, cart: $cart })
1@Link
2cart: CartItem[]
3
4 // ...
5 Badge({
6 value: this.cart.reduce((p, c) => p + c.count, 0) + '',
7 })
8
9 // ...
10 Span(this.cart.reduce((p, c) => p + (c.count * c.price * 100) / 100, 0).toFixed(2))
11 .fontColor('#fff')
12 .fontSize(24)
5)添加购物车
1CalcBtn({ icon: $r('app.media.ic_public_add_filled') })
2 .onClick(() => {
3 addCart(this.food)
4 promptAction.showToast({ message: '添加购物车成功' })
5 })
1)渲染购物车
1if (this.showCart) {
2 Cart({ cart: $cart })
3 }
1@Component
2export struct Cart {
3 @Link
4 cart: CartItemModel[]
5
6 // ...
7
8 List({ space: 30 }) {
9 ForEach(this.cart, (item:CartItemModel) => {
10 ListItem() {
11 CartItemComp({ item })
12 }
13 })
14 }
1import { CartItemModel } from '../models'
2import { CalcBtn } from './CalcBtn'
3
4@Component
5export struct CartItem {
6
7 @ObjectLink
8 item: CartItemModel
9
10 build() {
11 Row() {
12 Image(this.item.picture)
13 .width(60)
14 .aspectRatio(1)
15 .borderRadius(8)
16 Column({ space: 5 }) {
17 Text(this.item.name)
18 .fontSize(14)
19 .textOverflow({
20 overflow: TextOverflow.Ellipsis
21 })
22 .maxLines(2)
23 Row() {
24 Text() {
25 Span('¥ ')
26 .fontColor('#ff8000')
27 .fontSize(10)
28 Span(this.item.price.toFixed(2))
29 .fontColor('#ff8000')
30 .fontWeight(FontWeight.Bold)
31 }
32
33 Row() {
34 CalcBtn({ icon: $r('app.media.ic_screenshot_line'), plain: true })
35 Text(this.item.count+'')
36 .padding(10)
37 .fontSize(12)
38 CalcBtn({ icon: $r('app.media.ic_public_add_filled') })
39 }
40 }
41 .justifyContent(FlexAlign.SpaceBetween)
42 .width('100%')
43 }
44 .layoutWeight(1)
45 .alignItems(HorizontalAlign.Start)
46 .padding({ left: 10, right: 10 })
47 }
48 .alignItems(VerticalAlign.Top)
49 }
50}
2)购物车数量修改
1export const addCart = (food: FoodItem | CartItemModel) => { ... }
2
3export const delCart = (id: number) => {
4 const cart = getCart()
5 const f = cart.find(f => f.id === id)
6 if (f && f.count > 0) {
7 f.count--
8 }
9 AppStorage.Set(CART_KEY, JSON.stringify(cart))
10}
1CalcBtn({ icon: $r('app.media.ic_screenshot_line'), plain: true })
2 .onClick(() => {
3 delCart(this.item.id)
4 })
5 Text(this.item.count+'')
6 .padding(10)
7 .fontSize(12)
8 CalcBtn({ icon: $r('app.media.ic_public_add_filled') })
9 .onClick(()=>{
10 addCart(this.item)
11 })
3)清空购物车
1export const clearCart = () => {
2 AppStorage.Set(CART_KEY, '[]')
3}
1Text('清空购物车')
2 .fontSize(12)
3 .fontColor('#999')
4 .onClick(() => {
5 clearCart()
6 })