乐趣区

关于openharmony:OpenHarmony-31-Beta样例使用分布式菜单创建点餐神器

作者:刘丽红
随着社会的提高与倒退,科技伎俩的新陈代谢,餐饮行业也在寻求新的冲破与改革,手机扫描二维码点餐零碎曾经成为餐饮行业的将来趋势,倒退空间微小;扫码点餐,是“互联网 + 餐饮”潮流的产物,能够无效地为餐厅节俭人力老本,进步顾客点餐用餐效率,节俭顾客工夫,进步餐厅翻台率。然而,一些老年人也在面对扫码点餐时犯了难;还有些消费者不违心应用扫码点餐,是放心个人信息泄露等平安问题。如此,咱们设计了一款分布式菜单利用,不须要集体去关注公众号或下载小程序,服务员会提供几个点单的平板,连贯店铺网络,局域网内通信,这样大家点单、查看订单详情等都不受网络限度。先上成果:

润和 HiSpark Taurus AI Camera(Hi3516d)

润和大禹系列 HH-SCDAYU200 开发板

如上动图:可由一人拉终点单平板上的点单利用,大家可同时点单,点击菜品图片进入菜品详情页面,抉择口味后确认,就是一次点单实现,并主动返回到菜单首页;此时可看到下方购物车数量和总额的变动,点击下方 ” 点好了 ” 可进入订单具体,并通过分布式数据库让其他人也查看订单详情,从订单详情返回到点单页后,可再进行叠加点单;上面是 Demo 的开发阐明。

开发阐明

基于 OpenAtom OpenHarmony(以下简称“OpenHarmony”)3.1 beta 版本,并联合方舟开发框架(ArkUI)、分布式组网、分布式数据库等个性,应用 eTS 语言开发的一款分布式菜单利用;次要体现了 OpenHarmony 分布式数据库个性,依据设计师提供的 UX,首先就要思考分布式数据库应该要怎么设计,须要蕴含哪些元素;其次 demo 是没有后盾服务端,联合 ArkUI 框架,须要思考多个页面间数据怎么传递。

Demo 次要蕴含菜单首页、菜单详情页和订单详情页,以及退出菜单分布式数据库和结算订单分布式数据库。三个页面都须要订单列表数据,因为目前 ArkUI 框架在 app.ets 定义数据,其余页面不能间接援用,所以通过 router.push 的办法,带上 param 的参数,将数据在页面间进行传递。两个分布式数据库一个是订单列表数据,订单列表须要依据 UX 提供的设计图,来确认数据库中的元素,本 Demo 中的订单页面数据信息其余包含菜品的信息(图片、名称、份数、辣度等)以及点单人的信息(图片、名称和点单的数量);另一个是将下单胜利告诉所有人。

Demo 也还有很多待欠缺的点,比方:点击加 / 减的图标进行菜单的加减、一键清空订单、以及 Demo 是否有更好的计划来达到更好的点单体验等等,期待更多的读者们来欠缺。

代码构造如下图:

├─entry
│  └─src
│      └─main
│          │  config.json  // 利用配置文件
│          │  
│          ├─ets
│          │  └─MainAbility
│          │      │  app.ets  // 应用程序主入口
│          │      │  
│          │      ├─model
│          │      │      CommonLog.ets  // 日志类
│          │      │      MenuData.ets  // 初始化菜单数据类
│          │      │      MenuListDistributedData.ets  // 退出菜单分布式数据库
│          │      │      RemoteDeviceManager.ets  // 分布式拉起设施治理类
│          │      │      SubmitData.ets   // 结算订单分布式数据库
│          │      │      
│          │      └─pages
│          │              detailedPage.ets // 菜品具体页面
│          │              index.ets // 首页
│          │              menuAccount.ets // 订单详情页面
│          │              
│          └─resources
│              ├─base
│              │  ├─element
│              │  │      string.json
│              │  │      
│              │  ├─graphic
│              │  ├─layout
│              │  ├─media   // 寄存媒体资源
│              │  │      icon.png
│              │  │      icon_add.png
│              │  │      icon_back.png
│              │  │      icon_cart.png

页面编写

2.1 点单首页

效果图如上能够分为四局部

1)顶部页标签

@Component
struct PageInfo {build() {Flex() {Text('点单页')
        .fontSize('36lpx')
    }
    .padding({left: '48lpx', top: '28lpx'})
    .width('100%')
    .height('10%')
    .backgroundColor('#FFFFFF')
  }
}

2)用户信息
• 次要用到 Flex 容器 Image 和 Text 组件;
• 用户名称和头像图标,依据设施序列号不同,可展现不同的名称和图标;
• 点击右上角分享的小图标,可分布式拉起局域网内的另一台设施。

@Component
struct MemberInfo {
  @Consume userImg: Resource
  @Consume userName: string
  
  aboutToAppear() {
  // 依据设施序列号不同,展现不同的名称和图标
    CommonLog.info('==serial===' + deviceInfo.serial);
    if (deviceInfo.serial == '150100384754463452061bba4c3d670b') {this.userImg = $r("app.media.icon_user")
      this.userName = 'Sunny'
    }
    else {this.userImg = $r("app.media.icon_user_another")
      this.userName = 'Jenny'
    }
  }

  build() {Flex({ direction: FlexDirection.Column}) {Flex({ direction: FlexDirection.Row, alignItems: ItemAlign.Center}) {Image(this.userImg)
          .width('96lpx')
          .height('96lpx')
          .margin({right: '18lpx'})
        Text(this.userName)
          .fontSize('36lpx')
          .fontWeight(FontWeight.Bold)
          .flexGrow(1)
        Image($r("app.media.icon_share"))
          .width('64lpx')
          .height('64lpx')
      }
      // 关上分布式设施列表
      .onClick(() => {this.DeviceDialog.open()
      })
      .layoutWeight(1)
      .padding({left: '48lpx', right: '48lpx'})

      Flex({direction: FlexDirection.Row, alignItems: ItemAlign.Center}) {Column() {Text('124')
            .fontSize('40lpx')
            .margin({bottom: '24lpx'})
          Text('积分')
            .fontSize('22lpx')
            .opacity(0.4)
        }
        .flexGrow(1)

        Column() {Text('0')
            .fontSize('40lpx')
            .margin({bottom: '24lpx'})
          Text('优惠劵')
            .fontSize('22lpx')
            .opacity(0.4)
        }
        .flexGrow(1)

        Column() {Image($r("app.media.icon_member"))
            .width('48lpx')
            .height('48lpx')
            .margin({bottom: '24lpx'})
          Text('会员码')
            .fontSize('22lpx')
            .fontColor('#000000')
            .opacity(0.4)
        }
        .flexGrow(1)
      }
      .layoutWeight(1)
    }
    .width('93%')
    .height('25%')
    .borderRadius('16lpx')
    .backgroundColor('#FFFFFF')
    .margin({top: '24lpx', bottom: '32lpx'})
  }
}

3)列表展现
• 次要用到 Flex 容器和 Scroll 容器 Image 和 Text 组件;
• 从首页点击列表进入菜品具体页面,点菜胜利后会主动返回首页,此时列表须要动静更新菜品的数量。

@Component
struct MenuHome {private specialty: any[]
  private winterNew: any[]
  private classic: any[]
  private soup: any[]
  private menuItems: MenuData[]
  private titleList = ['招牌菜', '夏季新品', '下饭菜', '汤品']
  @State name: string = '招牌菜'

  build() {Flex({ direction: FlexDirection.Row, alignItems: ItemAlign.Start}) {Flex({ direction: FlexDirection.Column, justifyContent: FlexAlign.SpaceAround}) {
        ForEach(this.titleList, item => {Flex({ alignItems: ItemAlign.Center, justifyContent: FlexAlign.Start}) {Text(item)
              .fontSize('24lpx')
          }
          .padding({left: '24lpx'})
          .backgroundColor(this.name == item ? '#1A006A3A' : '#FFFFFF')
          .height('160lpx')
          .onClick(() => {
            this.name = item
            if (this.name == '招牌菜') {this.menuItems = initializeOnStartup(this.specialty);
            }
            else if (this.name == '夏季新品') {this.menuItems = initializeOnStartup(this.winterNew);
            }
            else if (this.name == '下饭菜') {this.menuItems = initializeOnStartup(this.classic);
            }
            else if (this.name == '汤品') {this.menuItems = initializeOnStartup(this.soup);
            }
          })
        }, item => item)
      }
      .width('20%')
      .backgroundColor('#FFFFFF')

      Flex({direction: FlexDirection.Column}) {Text(this.name)
          .fontSize('32lpx')
          .fontWeight(FontWeight.Bold)
          .opacity(0.4)
          .height('8%')
        Scroll() {Column() {List() {
              ForEach(this.menuItems, item => {ListItem() {MenuListItem({ menuItem: item})
                }
              }, item => item.id.toString())
            }
          }
        }
        .height('92%')
      }
      .margin({left: '10lpx'})
      .width('75%')

    }
    .height('50%')
  }
}

4)底部总额
• 次要用到 Flex 容器和 Stack 容器 Image 和 Text 组件;
• 从首页点击列表进入菜品具体页面,点菜胜利后会主动返回首页,更新订单数量和总额;
• 点击底部总额框,将订单列表退出分布式数据库,@entry 模仿监听数据库变动,拉起订单列表详情页面。

@Component
struct TotalInfo {@Consume TotalMenu: any[];
  private total: number = 0;
  private amount: number = 0;
  private remoteData: MenuListData

  aboutToAppear() {for (var index = 0; index < this.TotalMenu.length; index++) {this.total = this.total + this.TotalMenu[index].price * this.TotalMenu[index].quantity
      this.amount = this.amount + this.TotalMenu[index].quantity
    }
  }

  build() {Flex({ direction: FlexDirection.Row, alignItems: ItemAlign.Center}) {Stack({ alignContent: Alignment.Center}) {Image($r("app.media.icon_cart"))
          .width('96lpx')
          .height('96lpx')
          .margin({left: '22lpx'})
        Text(this.amount.toString())
          .backgroundColor('#F84747')
          .borderRadius('30plx')
          .fontSize('24plx')
          .textAlign(TextAlign.Center)
          .fontColor('#FFFFFF')
          .width('50lpx')
          .height('50lpx')
          .margin({left: '100lpx', bottom: '85lpx'})
      }
      .width('150lpx')
      .height('150lpx')

      Text('¥')
        .fontSize('22lpx')
        .fontColor('#006A3A')
        .margin({left: '22lpx'})
      Text(this.total.toString())
        .fontSize('40lpx')
        .fontColor('#006A3A')
        .flexGrow(1)
      Text('点好了')
        .height('100%')
        .width('35%')
        .fontColor('#FFFFFF')
        .backgroundColor('#F84747')
        .textAlign(TextAlign.Center)
    }
    // 将总的订单数据,退出分布式数据库
    .onClick(() => {this.remoteData.putData("menu_list", this.TotalMenu)
    })
    .width('100%')
    .height('10%')
    .backgroundColor('#FFFFFF')
  }
}

2.2 菜品详情页

效果图如上能够分为三局部

1)顶部页标签

@Component
struct PageInfo {build() {Flex() {Text('点单页')
        .fontSize('36lpx')
    }
    .padding({left: '48lpx', top: '28lpx'})
    .width('100%')
    .height('10%')
    .backgroundColor('#FFFFFF')
  }
}

2)菜单详情
• 次要用到 Flex 容器 Image 和 Text 组件 Button 组件;
• 辣度能够抉择。

@Component
struct detailInfo {
  private menuItem
  private spicyList = ['失常辣', '加辣', '少辣']
  @State spicy: string = '失常辣'
  private TotalMenu: any[]
  private index = 0
  private userName: string

  build() {Flex({ direction: FlexDirection.Column, alignItems: ItemAlign.Center}) {Flex({ direction: FlexDirection.Column, alignItems: ItemAlign.Start, justifyContent: FlexAlign.Start}) {Flex({ direction: FlexDirection.Row}) {Button()
            .backgroundColor('#006A3A')
            .width('8lpx')
            .height('48lpx')
            .margin({right: '12lpx'})
          Text('辣度')
        }
        .margin({left: '44lpx', top: '48lpx', bottom: '32lpx'})

        Flex({direction: FlexDirection.Row, justifyContent: FlexAlign.SpaceEvenly}) {
          ForEach(this.spicyList, item => {Button(item)
              .fontSize('28lpx')
              .height('60lpx')
              .width('156lpx')
              .borderRadius('12lpx')
              .backgroundColor(this.spicy == item ? '#006A3A' : '#0D000000')
              .fontColor(this.spicy == item ? '#FFFFFF' : '#000000')

              .onClick(() => {this.spicy = item})
          }, item => item)
        }
      }
      .margin({top: '56lpx'})
      .width('92%')
      .height('50%')
      .borderRadius('16lpx')
      .backgroundColor('#FFFFFF')
    }
  }
}

3)点击按钮
• 点击选好了,须要判断该菜品是否曾经在总订单外面,并判断是哪一个用户增加,依据判断,做出相应的减少。

Button('选好了')
       .fontSize('36lpx')
       .width('80%')
       .height('7%')
       .backgroundColor('#F84747')
       .onClick(() => {for (this.index = 0; this.index < this.TotalMenu.length; this.index++) {if (this.TotalMenu[this.index].name == this.menuItem.name && this.TotalMenu[this.index].spicy == this.spicy) {this.TotalMenu[this.index].quantity = this.TotalMenu[this.index].quantity + 1;
             if (this.userName == 'Sunny') {this.TotalMenu[this.index].userNumber = this.TotalMenu[this.index].userNumber + 1;
             } else if (this.userName == 'Jenny') {this.TotalMenu[this.index].anotherUserNumber = this.TotalMenu[this.index].anotherUserNumber + 1;
             }
             break;
           }
         }
         // 菜名不一样,辣度不一样,都须要从新 push 到列表外面
         if (this.index == this.TotalMenu.length) {
           this.menuItem.spicy = this.spicy;
           this.menuItem.quantity = 1;
           // 依据不必的用户名称,if (this.userName == 'Sunny') {this.menuItem.userNumber = 1;} else if (this.userName == 'Jenny') {this.menuItem.anotherUserNumber = 1;}
           this.TotalMenu.push(this.menuItem);
         }
         router.push({
           uri: 'pages/index',
           params: {menuItem: this.menuItem, TotalMenu: this.TotalMenu}
         })
       })
       .margin({top: '10%'})

2.3 订单详情页

成果如上能够分为三局部

1)订单列表
• 次要用到 Flex 容器 Image 和 Text 组件 Button 组件;
• 展现不同用户退出菜单数量。

@Component
struct TotalItem {
  private totalMenu: MenuData

  build() {Flex({ direction: FlexDirection.Column}) {Flex({ direction: FlexDirection.Row, alignContent: FlexAlign.Start, justifyContent: FlexAlign.Start}) {if (this.totalMenu.userNumber > 0) {Flex({ direction: FlexDirection.Row, alignItems: ItemAlign.Center}) {Image(this.totalMenu.userImg)
            .width('96lpx')
            .height('96lpx')
          Text(this.totalMenu.userName)
            .fontSize('36lpx')
            .fontWeight(FontWeight.Bold)
            .margin({left: '12lpx'})
            .flexGrow(1)
          Text(this.totalMenu.userNumber.toString())
            .fontSize('32lpx')
            .margin({right: '11plx'})

        }
        .height('150lpx')
      }
      if (this.totalMenu.anotherUserNumber > 0) {Flex({ direction: FlexDirection.Row, alignItems: ItemAlign.Center}) {Image(this.totalMenu.anotherUserImg)
            .width('96lpx')
            .height('96lpx')
          Text(this.totalMenu.anotherUserName)
            .fontSize('36lpx')
            .fontWeight(FontWeight.Bold)
            .margin({left: '12lpx'})
            .flexGrow(1)
          Text(this.totalMenu.anotherUserNumber.toString())
            .fontSize('32lpx')
            .margin({right: '11plx'})

        }
        .height('150lpx')
      }
    }
    .margin({top: '12lpx'})
    .borderRadius('16lpx')
    .padding({left: '3%', right: '3%', top: '2%'})
    .backgroundColor('#FFFFFF')
  }
}

2)下单按钮
• 点击下单,将 ”submitOk” 退出分布式数据库,监听数据库变动后,弹出自定义对话框。

@Component
struct SubmitList {
  private remoteData: SubmitData
  private SubmitOK: any[] = [
    {submit: "submitOk"}
  ];

  build() {Flex({ alignItems: ItemAlign.Center, justifyContent: FlexAlign.Center}) {Text('下单')
        .fontSize('36lpx')
        .fontColor('#FFFFFF')
    }
    .width('100%')
    .height('10%')
    .backgroundColor('#F84747')
    .onClick(() => {this.remoteData.putData("submit", this.SubmitOK)
    })
    .margin({top: '5%'})
  }
}

3)自定义弹框
• 通过 @CustomDialog 装璜器来创立自定义弹窗,应用形式可参考 自定义弹窗;
• 规定弹窗成果如下,弹窗组成由一个 Image 和两个 Text 竖向排列组成;
• 在 build() 下应用 Flex 容器来包裹,组件代码如下:

@CustomDialog
struct SubmitDialog {
  private controller: CustomDialogController

  build() {Flex({ direction: FlexDirection.Column, alignItems: ItemAlign.Center}) {Flex({ justifyContent: FlexAlign.Center}) {Image($r("app.media.icon_success"))
          .width('100lpx')
          .height('80lpx')
      }
      .flexGrow(1)

      Text('下单胜利')
        .fontSize('36lpx')
        .fontColor('#000000')
        .flexGrow(1)
      Text('* 舒适提醒:菜品具体售卖状况请以店面理论状况为准哦~')
        .fontSize('22lpx')
        .opacity(0.6)
        .fontColor('#000000')
        .padding({left: '10lpx', right: '10lpx'})
    }
    .height('300lpx')
    .width('100%')
    .padding({top: '50lpx', bottom: '20lpx'})

  }
}

• 在 @entry 模块创立 CustomDialogController 对象并传入弹窗所需参数,设置点击容许点击遮障层退出,通过 open() 办法,显示弹窗。

SubmitDialog: CustomDialogController = new CustomDialogController({builder: SubmitDialog(),
    autoCancel: true
  })
aboutToAppear() {this.remoteData.createManager(() => {
      let self = this
      var data;
      if (JSON.stringify(self.remoteData.dataItem).length > 0) {
        data = self.remoteData.dataItem;
        CommonLog.info("======submit==" + data[0].submit);
        if (data[0].submit == "submitOk") {this.SubmitDialog.open()
        }
      }
    }, "com.distributed.order", "submit")
  }

分布式数据管理

OpenHarmony 技术个性包含分布式数据管理、分布式任务调度等;分布式数据管理基于分布式软总线的能力,实现应用程序数据和用户数据的分布式治理。用户数据不再与繁多物理设施绑定,业务逻辑与数据存储拆散,跨设施的数据处理如同本地数据处理一样方便快捷,让开发者可能轻松实现全场景、多设施下的数据存储、共享和拜访,为打造统一、晦涩的用户体验发明了根底条件。

3.1 开发步骤
分布式数据管理要求两个或多个设施在同一网络;开发步骤:

1)导入模块
import distributedData from ‘@ohos.data.distributedData’;

2)创立一个 KVManager 对象实例,用于治理数据库对象;留神 bundleName 须要批改为本人的包名。

let kvManager;
try {
    const kvManagerConfig = {
        bundleName : 'com.distributed.order',
        userInfo : {
            userId : '0',
            userType : distributedData.UserType.SAME_USER_ID
        }
    }
    distributedData.createKVManager(kvManagerConfig, function (err, manager) {if (err) {console.log("createKVManager err:"  + JSON.stringify(err));return;}
        console.log("createKVManager success")
        kvManager = manager
    });
} catch (e) {console.log("An unexpected error occurred. Error:" + e);}

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

let kvStore;
let kvManager;
try {
    const options = {
        createIfMissing : true,
        encrypt : false,
        backup : false,
        autoSync : true,
        kvStoreType : distributedData.KVStoreType.SINGLE_VERSION,
        securityLevel : distributedData.SecurityLevel.S2,
    };
    kvManager.getKVStore('storeId', options, function (err, store) {if (err) {console.log("getKVStore err:"  + JSON.stringify(err));return;}
        console.log("getKVStore success");
        kvStore = store
    });
} catch (e) {console.log("An unexpected error occurred. Error:" + e);}

4)KVStore 数据库实例,KVStore.put 提供减少数据的办法,如下是参数阐明。

let kvStore;
try {kvStore.put(key, value, function (err,data) {if (err != undefined) {console.log("put err:" + JSON.stringify(err))
            return;
        }
        console.log("put success");
    });
}catch (e) {console.log("An unexpected error occurred. Error:" + e);}

5)KVStore 数据库实例,KVStore.on 订阅指定类型的数据变更告诉;个别监听远端设施变动,再进行相应操作达到分布式数据共享的成果。

let kvStore;
kvStore.on('dataChange', distributedData.SubscribeType.SUBSCRIBE_TYPE_LOCAL, function (data) {console.log("dataChange callback call data:" + JSON.stringify(data));
});

6)具体开发请参考分布式数据管理
https://gitee.com/openharmony…

3.2 利用示例

本我的项目通过 storeId(数据库惟一标识符)值不同,创立了两个数据管理类,别离是 MenuListData 类和 SubmitData 类,即 Demo 中的 MenuListDistributedData.ets SubmitData.ets 文件。

1)MenuListData 是将残缺订单增加到分布式数据库,当监听到数据库变动时,获取残缺订单列表,并通过 router.push 接口将数据传递到订单详情页面展现。

• 创立一个 MenuListData 类

private remoteData: MenuListData = new MenuListData()

• 定义一个订单列表汇合,汇合中的元素包含菜单信息和点单用户信息;点菜后就依据菜品名称和辣度的不同,对订单数据汇合进行批改或减少。

TotalMenu: any[] = [
  {
    "imgSrc": "",   // 菜品图片"name":"",     // 菜品名称
    "remarks": "",  // 菜品备注"price": 0,     // 菜品价格"quantity": 0,  // 菜品数量"spicy":"",    // 辣度
    "userImg": ,    
    "userName": "","userNumber": 0,"anotherUserImg":"",
    "anotherUserName": "","anotherUserNumber": 0,
  }];

• 将订单数据退出到分布式数据库

 this.remoteData.putData("menu_list", this.TotalMenu)

• 监听数据库变动,获取数据,并将数据传递到 menuAccount 订单详情页面;

this.remoteData.createManager(() => {
      let self = this
      var data
      if (JSON.stringify(self.remoteData.dataItem).length > 0) {
        data = self.remoteData.dataItem
        var list = []
        for (var i = 0; i < data.length; i++) {list[i] = data[i]
        }
        router.push({
          uri: 'pages/menuAccount',
          params: {TotalMenu: list}
        })
      }
    })

2)SubmitData 在订单结算时点击下单,将 submitOK 汇合增加到数据库,示意下单实现,监听到数据库变动,各设施弹出下单胜利提示框;

• 创立一个 SubmitData 类

private remoteData: SubmitData = new SubmitData();

• 定义一个 SubmitOK 汇合,这里用汇合次要是为了解决数据不便
private SubmitOK: any[] = [

{submit: "submitOk"}

]

• 增加 SubmitOK 汇合到数据库中
this.remoteData.putData(“submit”, this.SubmitOK)

• 监听到数据库变动,获取数据并比拟是 submitOK 后,弹出提示框,告知所有人下单胜利。

this.remoteData.createManager(() => {
      let self = this
      var data
      if (JSON.stringify(self.remoteData.dataItem).length > 0) {
        data = self.remoteData.dataItem
        if (data[0].submit == "submitOk") {this.SubmitDialog.open()
        }
      }
    })

更残缺的分布式数据库的应用,请参考 Demo。

我的项目下载和导入

我的项目地址:https://gitee.com/openharmony…

1)git 下载
git clone https://gitee.com/openharmony…

2)我的项目导入
关上 DevEco Studio,点击 File->Open-> 下载门路 /FA/Shopping/DistributedOrder

3)硬件束缚
须要下载对应开发板镜像 (https://gitee.com/openharmony…) 进行烧录;如下图:

分享与共建

丰盛多样的 OpenHarmony 开发样例离不开宽广合作伙伴和开发者的奉献,如果你开发出了更好的 OpenHarmony 开发样例,并违心分享给宽广开发者学习,请 Fork 并 Pull Request 到如下仓库,共建 OpenHarmony 开发样例。若您不分明如何提交代码到仓库,请参考代码奉献教程,咱们等着你的 Pull Request。(https://gitee.com/openharmony…)。

退出移动版