作者:刘丽红
随着社会的提高与倒退,科技伎俩的新陈代谢,餐饮行业也在寻求新的冲破与改革,手机扫描二维码点餐零碎曾经成为餐饮行业的将来趋势,倒退空间微小;扫码点餐,是“互联网+餐饮”潮流的产物,能够无效地为餐厅节俭人力老本,进步顾客点餐用餐效率,节俭顾客工夫,进步餐厅翻台率。然而,一些老年人也在面对扫码点餐时犯了难;还有些消费者不违心应用扫码点餐,是放心个人信息泄露等平安问题。如此,咱们设计了一款分布式菜单利用,不须要集体去关注公众号或下载小程序,服务员会提供几个点单的平板,连贯店铺网络,局域网内通信,这样大家点单、查看订单详情等都不受网络限度。先上成果:
润和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)顶部页标签
@Componentstruct PageInfo { build() { Flex() { Text('点单页') .fontSize('36lpx') } .padding({ left: '48lpx', top: '28lpx' }) .width('100%') .height('10%') .backgroundColor('#FFFFFF') }}
2)用户信息
• 次要用到 Flex 容器 Image 和 Text 组件;
• 用户名称和头像图标,依据设施序列号不同,可展现不同的名称和图标;
• 点击右上角分享的小图标,可分布式拉起局域网内的另一台设施。
@Componentstruct 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 组件;
• 从首页点击列表进入菜品具体页面,点菜胜利后会主动返回首页,此时列表须要动静更新菜品的数量。
@Componentstruct 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 模仿监听数据库变动,拉起订单列表详情页面。
@Componentstruct 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)顶部页标签
@Componentstruct PageInfo { build() { Flex() { Text('点单页') .fontSize('36lpx') } .padding({ left: '48lpx', top: '28lpx' }) .width('100%') .height('10%') .backgroundColor('#FFFFFF') }}
2)菜单详情
• 次要用到 Flex 容器 Image 和 Text 组件 Button 组件;
• 辣度能够抉择。
@Componentstruct 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 组件;
• 展现不同用户退出菜单数量。
@Componentstruct 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" 退出分布式数据库,监听数据库变动后,弹出自定义对话框。
@Componentstruct 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 容器来包裹,组件代码如下:
@CustomDialogstruct 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...)。