OpenHarmony 实战开发——分布式购物车案例展示~

简介

分布式购物车demo 模拟的是我们购物时参加满减活动,进行拼单的场景;实现两人拼单时,其他一人添加商品到购物车,另外一人购物车列表能同步更新,且在购物车列表页面结算时,某一人结算对方也能实时知道结算金额和优惠金额。整个操作效果分为3个小动画,

  • 拉起对方用户
  • 添加商品到购物车列表
  • 购物车列表勾选
  • demo效果(HH-SCDAYU200)

工程目录

完整的项目结构目录如下

├─entry\\src\\main
│          │  config.json  应用配置文件
│          │ 
│          ├─ets
│          │  └─MainAbility
│          │      │  app.ets  ets应用程序主入口
│          │      │ 
│          │      ├─model
│          │      │      ArsData.ets     // 初始化我的页面数据
│          │      │      CommonLog.ets   // 日志类
│          │      │      GoodsData.ets   // 初始化商品信息数据类
│          │      │      MenuData.ets    // 初始化我的页面数据类
│          │      │      RemoteDeviceManager.ets  // 分布式拉起设备管理类
│          │      │      ShoppingCartDistributedData.ets  // 加入购物车分布式数据库
│          │      │      TotalSelectedDistributedData.ets // 结算购物车分布式数据库
│          │      │ 
│          │      └─pages
│          │              DetailPage.ets   // 商品详情页面
│          │              HomePage.ets     // 应用首页
│          │              MyPage.ets       // 我的页面
│          │              ShoppingCartListPage.ets  // 购物车列表页面
│     └─resources // 静态资源目录
│         ├─base
│         │  ├─element
│         │  ├─graphic
│         │  ├─layout
│         │  ├─media // 存放媒体资源
│         │  └─profile
│         └─rawfile

开发步骤

1. 新建OpenHarmony ETS项目

在DevEco Studio中点击File -> New Project ->[Standard]Empty Ability->Next,Language 选择ETS语言,最后点击Finish即创建成功。

2. 编写商品展示主页面

效果图如上可以分为两部分

2.1商品列表展示

1)首先在@entry组件入口build()中使用 Tabs作为容器,达到排行榜和推荐翻页的效果;

2)再通过 List 包裹 Row 布局依次写入 Column 包裹的三个 Text 组件和 Image 组件;

3)并通过 Navigator 组件实现点击商品跳转到商品详细页功能,页面跳转过程使用 pageTransition 转场动画

Tabs() {
       TabContent() {
         GoodsList({ goodsItems: this.goodsItems});
       }
       .tabBar("畅销榜")
       .backgroundColor(Color.White)

       TabContent() {
         GoodsList({ goodsItems: this.goodsItems});
       }
       .tabBar("推荐")
       .backgroundColor(Color.White)
     }
      Navigator({ target: 'pages/DetailPage' }) {
       Row({ space: '40lpx' }) {
         Column() {
           Text(this.goodsItem.title)
             .fontSize('28lpx')
           Text(this.goodsItem.content)
             .fontSize('20lpx')
           Text('¥' + this.goodsItem.price)
             .fontSize('28lpx')
             .fontColor(Color.Red)
         }
         .height('160lpx')
         .width('50%')
         .margin({ left: '20lpx' })
         .alignItems(HorizontalAlign.Start)

         Image(this.goodsItem.imgSrc)
           .objectFit(ImageFit.ScaleDown)
           .height('160lpx')
           .width('40%')
           .renderMode(ImageRenderMode.Original)
           .margin({ right: '20lpx', left: '20lpx' })

       }
       .height('180lpx')
       .alignItems(VerticalAlign.Center)
       .backgroundColor(Color.White)
     }
     .params({ goodsItem: this.goodsItem ,ShoppingCartsGoods:this.ShoppingCartsGoods})
     .margin({ left: '40lpx' })
   }
   // 转场动画使用系统提供的多种默认效果(平移、缩放、透明度等)
 pageTransition() {
   PageTransitionEnter({ duration: 1000 })
     .slide(SlideEffect.Left)
   PageTransitionExit({ duration: 1000  })
     .slide(SlideEffect.Right)
 }
2.2底部导航栏

1)通过 Row 包裹三个 Image 组件,并添加onClick 点击事件,修改 @Consume 修饰的变量,从而改变 @Provide 装饰的变量,再通过条件渲染展示不同的页面内容;

Flex() {
        Image(this.iconPath[0])
          .objectFit(ImageFit.Cover)
          .height('60lpx')
          .width('60lpx')
          .margin({left:'50lpx',right:'40lpx'})
          .onClick(() => {
            this.iconPath[0] = this.iconPathSelectsTmp[0]
            this.iconPath[1] = this.iconPathTmp[1]
            this.iconPath[2] = this.iconPathTmp[2]
            this.currentPage = 1
          })
        Image(this.iconPath[1])
          .objectFit(ImageFit.Cover)
          .height('60lpx')
          .width('60lpx')
          .margin({left:'40lpx',right:'40lpx'})
          .onClick(() => {
            this.iconPath[0] = this.iconPathTmp[0]
            this.iconPath[1] = this.iconPathSelectsTmp[1]
            this.iconPath[2] = this.iconPathTmp[2]
            this.currentPage = 2
            this.remoteData.putData("shopping_cart", this.ShoppingCartsGoods)
          })
        Image(this.iconPath[2])
          .objectFit(ImageFit.Cover)
          .height('60lpx')
          .width('60lpx')
          .margin({left:'40lpx',right:'50lpx'})
          .onClick(() => {
            this.iconPath[0] = this.iconPathTmp[0]
            this.iconPath[1] = this.iconPathTmp[1]
            this.iconPath[2] = this.iconPathSelectsTmp[2]
            this.currentPage = 3
          })
      }
    .margin({top:'20lpx'})
    }
      
      Column() {
          if (this.currentPage == 1) {
            Flex({ direction: FlexDirection.Row, justifyContent: FlexAlign.End }) {
              Image($r("app.media.icon_share"))
                .objectFit(ImageFit.Cover)
                .height('60lpx')
                .width('60lpx')
            }
            .width("100%")
            .margin({ top: '20lpx', right: '50lpx' })
            .onClick(() => {
              this.playerDialog.open()
            })

            GoodsHome({ goodsItems: this.goodsItems})
          }
          else if (this.currentPage == 3) {
            //我的
            MyInfo()
          }
        }
3. 编写商品详细页面
3.1顶部滑动组件

1)滑动容器,提供切换子组件显示的能力;

Swiper() {
        ForEach(this.detailImages, item => {
          Image(item)
            .height('400lpx')
            .width('100%')
        })
      }
      .index(0)
      .autoPlay(true)
      .interval(3000)
      .indicator(true)
      .loop(true)
      .height('440lpx')
      .width('100%')
3.2 自定义弹框

1)通过 @CustomDialog 装饰器来创建自定义弹窗,使用方式可参考 自定义弹窗

2)规则弹窗效果如下,弹窗组成由两个 Text 和两个 Button 竖向排列组成;

所有我们可以在build()下使用 Flex 容器来包裹,组件代码如下:

@CustomDialog
struct CustomDialogExample {
  controller: CustomDialogController
  cancel: () => void
  confirm: () => void
  ShoppingCartsGoods: any[]

  build() {
    Flex() {
      Flex({ direction: FlexDirection.Column, justifyContent: FlexAlign.Center, alignItems: ItemAlign.Center }) {
        Text('加入购物车成功')
          .fontColor("#000000")
          .fontSize('40lpx')
          .margin({ top: '20lpx', bottom: "20lpx" })

        Flex({ justifyContent: FlexAlign.SpaceAround }) {
          Button('取消')
            .onClick(() => {
              this.controller.close()
              this.cancel()
            }).backgroundColor(0xffffff).fontColor(Color.Black)
          Button('确定')
            .onClick(() => {
              this.controller.close()
              this.confirm()
            }).backgroundColor(0xffffff).fontColor(Color.Red)
        }.margin({ bottom: "20lpx" })
      }
    }
    .height('200lpx')
  }
}

3)在@entry创建CustomDialogController对象并传入弹窗所需参数,后面可通过该对象open()和close()方法进行打开和关闭弹窗;

dialogController: CustomDialogController = new CustomDialogController({
    builder: CustomDialogExample({
      cancel: this.onCancel,
      confirm: this.onAccept,
      ShoppingCartsGoods: this.ShoppingCartsGoods
    }),
    cancel: this.existApp,
    autoCancel: true
  })
  onCancel() {
    CommonLog.info('Callback when the first button is clicked')
  }

  onAccept() {
    CommonLog.info('Callback when the second button is clicked')
    router.push({
      uri: "pages/HomePage",
      params: { dataList: this.ShoppingCartsGoods }
    })
  }

  existApp() {
    CommonLog.info('Click the callback in the blank area')
  }
4. 添加分布式流转

分布式流转需要在同一网络下通过 DeviceManager组件 进行设备间发现和认证,获取到可信设备的deviceId调用 featureAbility.startAbility ,即可把应用程序流转到另一设备。

1)创建DeviceManager实例;

2)调用实例的startDeviceDiscovery(),开始设备发现未信任设备;

3)设置设备状态监听on('deviceFound',callback),获取到未信任设备,并用discoverList变量进行维护;

4)传入未信任设备参数,调用实例authenticateDevice方法,对设备进行PIN码认证;

5)若是已信任设备,可通过实例的getTrustedDeviceListSync()方法来获取设备信息;

6)将设备信息中的deviceId传入 `featureAbility.startAbility方法,实现流转;

7)流转接收方可通过 featureAbility.getWant()获取到发送方携带的数据;

项目中将上面设备管理封装至RemoteDeviceManager,通过RemoteDeviceManager的四个方法来动态维护deviceList设备信息列表,实现分布式流转只需要在deviceList中获取deviceId,然后调用featureAbility.startAbility并携带数据,即可实现分布式流转。

5.分布式数据管理

分布式数据管理 要求两个或多个设备在同一网络,才能监听到数据库的改变,从而渲染页面;开发步骤:

1)创建一个KVManager对象实例,用于管理数据库对象;

2)通过指定Options和storeId,创建并获取KVStore数据库,如下是参数说明;需要先通过createKVManager构建一个KVManager实例;

参数名 类型 必填 说明
storeId string 数据库唯一标识符,长度不大于 MAX_STORE_ID_LENGTH。
options Options 创建KVStore实例的配置信息。

3)KVStore数据库实例, KVStore.put提供增加数据的方法,如下是参数说明;

参数名 类型 必填 说明
key string 要添加数据的key,不能为空且长度不大于 MAX_KEY_LENGTH 。
value Uint8Array string number
callback AsyncCallback 回调函数。

4) KVStore数据库实例,KVStore.on订阅指定类型的数据变更通知;一般监听远端设备变化,再进行相应操作达到分布式数据共享的效果;

本d项目通过storeId 值不同,创建了两个数据库,分别是ShoppingCartsInfo类和TotalData类,ShoppingCartsInfo应用添加商品到购物车,TotalData应用在购物车列表进行勾选结算;如下是TotalData类流程

如下是ShoppingCartsInfo类流程

项目下载和导入

1)git下载

git clone https://gitee.com/openharmony-sig/knowledge_demo_shopping.git  --depth=1

2)项目导入

打开DevEco Studio,点击File->Open->下载路径/FA/Shopping/DistributedShoppingCart

写在最后

  • 如果你觉得这篇内容对你还蛮有帮助,我想邀请你帮我三个小忙:
  • 点赞,转发,有你们的 『点赞和评论』,才是我创造的动力。
  • 关注小编,同时可以期待后续文章ing🚀,不定期分享原创知识。
  • 想要获取更多完整鸿蒙最新学习资源,请移步前往小编:https://gitee.com/MNxiaona/733GH
相关推荐
lqj_本人1 小时前
鸿蒙next版开发:相机开发-会话管理(ArkTS)
华为·harmonyos
东方巴黎~Sunsiny1 小时前
如何优化Kafka消费者的性能
分布式·kafka
NAMELZX1 小时前
Kafka常见问题及处理
分布式·kafka
huaqianzkh2 小时前
了解Hadoop:大数据处理的核心框架
大数据·hadoop·分布式
jlting1953 小时前
Kafka--关于broker的夺命连环问
分布式·kafka
lqj_本人3 小时前
鸿蒙next版开发:分析JS Crash(进程崩溃)
华为·harmonyos
菜菜-plus4 小时前
分布式,微服务,SpringCloudAlibaba,nacos,gateway,openFeign
java·分布式·微服务·nacos·gateway·springcloud·openfeign
Harmony_QI4 小时前
鸿蒙北向开发环境安装指南
华为·harmonyos·鸿蒙
好奇的菜鸟4 小时前
RabbitMQ 通道(Channel)详解:方法使用、消息确认与拒绝
分布式·rabbitmq