一个餐饮APP开发,主要包括以下几个核心步骤和功能:
- 需求和功能规划:首先需要确定APP的基本功能,如商品展示、在线点餐、订单管理、支付结算、扫码核销等。同时需要实现多门店特色功能,如门店切换与定位、库存管理和销售数据统计等。此外,还有营销功能、如优惠券发放、会员制度、积分兑换等,以吸引顾客和提高用户的粘性。
- 选择开发方式:如企业有自己的技术团队,可以选择自主研发,以实现高度定制化。如没有,则可以选择第三方开发平台或外包给专业的软件公司,以降低成本和提高效率。
- 申请账号和认证:注册相关的账号并进行企业主体认证,以确保可以使用APP的更多功能,如支付接口等。
- 设计与开发:设计简洁、美观、易用的界面,确保各个功能模块的正常运行和数据的准确传输。特别是多门店管理系统的开发,需要建立一个集中的管理后台,用于管理各个门店的信息、菜品、库存、订单等,以使总部可以统一管理和监控。
- 测试与优化:在开发完成后,进行功能测试和优化,确保APP的稳定性和用户体验。测试内容包括订单处理、支付功能、用户界面等方面。
- 多门店管理:实现多门店的统一管理和调试,实时监控各门店的运营情况,确保高效顺畅。
- 营销工具:支持优惠券、满减活动、会员积会等多种促销方式,提升客户黏性和销售额。
- 支付功能:支持多种支付方式,确保支付案例,提升顾客满意度。
首先我们先完成APP的首页开发,其中包含商品展示轮播图、应用分类、商品列表、底部导航等功能。
一、环境搭建
1.1 DevEco Studio
如果DevEco Studio应用已安装,则直接打开并创建项目即可。
如系统中未安装DevEco Studio开发工具,可前云官方网站下载,地址:DevEco Studio-HarmonyOS Next Beta版-华为开发者联盟
安装步骤地址:HarmonyOS开发之DevEco Studio安装_select the directory where ohpm has been installed-****博客
2.2 仿真机安装
如果你没有华为系统手机,可以点击并打开Device Manager管理界面,选择“Local Emulator”本地仿真器的创建。
在Device Manager管理界面的右下角,点击“New Emulator”新仿真器创建按钮,选择您需要创建的仿真设备即可,这里选择“Huawei_Phone”。
然后点击下一步,进入虚拟设备的配置界面,选择您需要的版本即可。这里选择最新版本(注意:在点击”下一步“按钮前,需要点击Name列中下载图标,将需要安装的对应版本先下载,否则会提示“Download the system image first”错误),如下图:
点击“下一步”后,会进入新界面,自定义设备名称后,点击“完成”即可。如下图:
当仿真设备创建完成后,最初管理界面会多出新创建仿真设备信息,如下图:
此时,点击右侧“播放”按钮,则可以启动仿真设备了。如下图:
二、创建APP应用
创建项目应用,首先打开DevEco Studio开发工具,点击“File” -> “New” -> “Create Project”,打开
选择创建一个空应用,点击“下一步”。
修改项目名称后,点击“完成”即可。
此时项目则已创建成功,如下图:
2.1 Previewer预览
点击右侧的“previewer”即可查看界面效果,如下图:
2.2 仿真设备预览
当仿真设备启动后,在DevEco Studio中会显示创建的仿真设备的名称,此时点击右侧“Run”或“Debug”即可在仿真设备中预览界面效果了。
点击“Run”后,刚创建的项目界面,则在仿真设备中显示了,如下图:
三、餐饮APP首页
在介绍完HarmonyOS项目开发中,对DevEco Studio安装、仿真设备创建后,接下来将通过HarmonyOS完成如下图的首页的搭建工作。
上图原型已上传到资源中,需要的朋友可以前去下载,地址:https://download.****.net/download/jiciqiang/89906021
图片资源,可下载原型资源包后,打开压缩包并解压,在image目录中查看。如下图:
3.1 自定义组件 - 低代码开发
在创建顶部导航前,我们先在ets目录中新建components目录,用于存放自定义组件。
当components创建成功后,在其内部创建Header.ets文件,用于自定义顶部导航组件。
选中components目录并右击,创建组件,如下图:
此操作会创建出一个低代码操作界面,可以帮助开发人员快速搭建界面布局及样式设置。如果您还是习惯于手动代码搭建,可以直接在components目录中创建ets文件,定义Header组件。低代码界面如下图:
当我们点击“生成est文件”转换按钮后,会弹出要生成的代码弹框,如下图:
进行转换后再打开Header.ets文件,则代码已经生成。此时大家会发现,低代码文件自动删除了,这操作是不可逆的过程。
说明:使用低代码开发界面过程中,如果界面需要使用到其他暂时不支持可视化布局的控件,可以在低代码界面开发完成后,单独“转换”按钮,将低代码界面转换为ets文件。
注意:代码转换操作会删除visual文件及其父目录,且为不可逆过程,代码转换后不能通过html/css或ets文件反向生成visual文件。多设备开发的场景,可以单击界面画布query模式,可以为组件设置不同的样式和属性。当前media query模式仅针对不同设备类型和不同屏幕状态(横屏/竖屏)有效。
DevEco低代码文档地址:文档中心
3.2 自定义组件 - 顶部导航
顶部导航组件开内容和样式开发完成后,可以打开pages目录下的index.ets文件,将Header组件引入进去后,再来查看下效果。
代码如下:
import Header from '../components/Header'
@Entry
@Component
struct Index {
@State message: string = 'Hello World'
build() {
Row() {
Column() {
Header()
Text(this.message)
.fontSize(50)
.fontWeight(FontWeight.Bold)
}
.width('100%')
}
.height('100%').alignItems(VerticalAlign.Top)
}
}
界面效果如下图:
界面内容和样式虽然已完成了,但是当搜索内容改变时,需要将输入文本上报给父组件,从而做出相应操作及跳转动作。所以我们在Header.ets文件添加相应变量、方法和事件,将信息传递给父组件。
components/Header.ets代码如下:
@Preview
@Component
export default struct Header {
// 搜索关键词
@State keyword: string = ''
// 关键词内容改变时回调函数
private onSearchChange: (data: string) => void
build() {
Row() {
Image($rawfile('position_u5.png'))
.width("16vp")
.height("25vp")
.margin({ top: "0vp", bottom: "0vp", left: "0vp", right: "0vp" })
Text("门店信息\n")
.width("65vp")
.height("40vp")
.fontSize("12fp")
TextInput({ text: this.keyword, placeholder: "请输入搜索关键词" })
.width("170vp")
.height("40vp")
.placeholderColor("#262626")
.placeholderFont({ size: 12 })
.onChange((e) => this.keyword = e)
Button("搜索")
.width("70vp")
.height("40vp")
.onClick(() => {
this.onSearchChange(this.keyword) // 点击查询事件,将信息传递给父组件
})
}
.width("100%")
.padding({ top: "10vp", bottom: "10vp", left: "15vp", right: "15vp" })
.justifyContent(FlexAlign.SpaceBetween)
}
}
pages/Index.ets代码如下:
import Header from '../components/Header'
@Entry
@Component
struct Index {
@State keyword: string = ''
build() {
Row() {
Column() {
Header({onSearchChange: this.onSearchChange.bind(this)})
}
.width('100%')
}
.height('100%').alignItems(VerticalAlign.Top)
}
/**
* 搜索内容改变事件
* @param data
*/
onSearchChange(data: string){
console.log('search', data)
}
}
此时点击顶部导航栏中“搜索”按钮,则可以在父组件中输出输入框中的内容了。如下图:
该篇目前只讲首页搭建部分内容,所以搜索功能后续操作暂时不再详说,后续有机会或者大家自行追加即可。
3.3 自定义组件 - 底部菜单
接下来我们来完善下底部菜单导航的开发工作,这里将使用到UI界面的Tabs组件。
3.3.1 Tabs组件
Tabs组件的页面组成包含两个部分,分别是TabContent和TabBar。TabContent是内容页,TabBar是导航页签栏,页面结构如下图所示,根据不同的导航类型,布局会有区别,可以分为底部导航、顶部导航、侧边导航,其导航栏分别位于底部、顶部和侧边。
说明:
- TabContent组件不支持设置通用宽度属性,其宽度默认撑满Tabs父组件。
- TabContent组件不支持设置通用高度属性,其高度由Tabs父组件高度与TabBar组件高度决定。
Tabs使用花括号包裹TabContent,如下图,其中TabContent显示相应的内容页。
官方文档地址:文档中心
3.3.2 实现Tabs菜单
了解了Tabs结构后,我们使用它完成底部导航菜单代码部分。visual低代码模式中也有Tabs组件,由于这里没有复杂的样式结构,直接在页面中定义即可。
pages/index.ets代码如下:
import Header from '../components/Header'
@Entry
@Component
struct Index {
@State keyword: string = ''
build() {
Row() {
Column() {
Tabs({
barPosition: BarPosition.End
}){
TabContent(){
Header({onSearchChange: this.onSearchChange.bind(this)})
}.tabBar({icon: $rawfile('u48.png'), text: '首页'})
TabContent(){
Text('待开发中...')
}.tabBar({icon: $rawfile('u43.png'), text: '订单'})
TabContent(){
Text('待开发中...')
}.tabBar({icon: $rawfile('u53.png'), text: '我的'})
}
.barMode(BarMode.Fixed)
.barHeight(60)
}
.width('100%')
}
.height('100%').alignItems(VerticalAlign.Top)
}
/**
* 搜索内容改变事件
* @param data
*/
onSearchChange(data: string){
console.log('search value:', data)
}
}
页面效果如下图:
3.4 自定义构建函数 - 轮播图
Swiper组件提供滑动轮播显示的能力。Swiper本身是一个容器组件,当设置了多个子组件后,可以对这些子组件进行轮播显示。通常,在一些应用首页显示推荐的内容时,需要用到轮播显示的能力。
在components/Index目录中创建轮播图组件,文件结构如下图:
代码如下:
@Preview
@Component
export default struct CarouselMap {
private swiperController: SwiperController = new SwiperController()
build() {
Row() {
Swiper(this.swiperController){
Image($rawfile('u13.png')).borderRadius(10)
Image($rawfile('u13.png')).borderRadius(10)
}
.width("100%")
.height("200vp")
.padding(10)
.index(0) // 默认索引
.autoPlay(true) // 自动播放
.loop(true) // 循环播放
.itemSpace(10) // 图片的间隙
.onChange((index: number) => {
console.log('testTag swiper index:', index)
})
}
}
}
当轮播图组件定义好后,将期引入到首页中,pages/index.ets代码如下:
import Header from '../components/Header'
import CarouseMap from '../components/Index/CarouselMap'
@Entry
@Component
struct Index {
@State keyword: string = ''
build() {
Row() {
Column() {
Tabs({
barPosition: BarPosition.End
}){
TabContent(){
// 增加Column原因是因为TabContent中只有一个子组件,否则会报错
Column(){
Header({onSearchChange: this.onSearchChange.bind(this)})
CarouseMap()
}.width('100%').height('100%').alignItems(HorizontalAlign.Start)
}.tabBar({icon: $rawfile('u48.png'), text: '首页'})
TabContent(){
Text('待开发中...')
}.tabBar({icon: $rawfile('u43.png'), text: '订单'})
TabContent(){
Text('待开发中...')
}.tabBar({icon: $rawfile('u53.png'), text: '我的'})
}
.barMode(BarMode.Fixed)
.barHeight(60)
}
.width('100%')
}
.height('100%').alignItems(VerticalAlign.Top)
}
/**
* 搜索内容改变事件
* @param data
*/
onSearchChange(data: string){
console.log('search value:', data)
}
}
页面效果如下图:
3.5 自定义构建函数 - 分类列表
接下来完成分类列表信息,在components/Index目录中创建CategoryList.ets组件。
创建后visual低代码编辑界面,如下图:
当界面内容搭建完后,通过转换按钮,生成分类列表的组件代码。代码如下:
@Preview
@Component
export default struct MiddleMenu {
build() {
Row() {
Column() {
Image($rawfile('u27.png'))
.width("64vp")
.height("64vp")
Text("门店")
.height("32vp")
.textAlign(TextAlign.Center)
.fontSize("16fp")
.fontWeight(FontWeight.Bold)
}
Column() {
Image($rawfile('u25.png'))
.width("64vp")
.height("64vp")
Text("水果")
.height("32vp")
.textAlign(TextAlign.Center)
.fontSize("16fp")
.fontWeight(FontWeight.Bold)
}
Column() {
Image($rawfile('u27.png'))
.width("64vp")
.height("64vp")
Text("美食")
.height("32vp")
.textAlign(TextAlign.Center)
.fontSize("16fp")
.fontWeight(FontWeight.Bold)
}
Column() {
Image($rawfile('u29.png'))
.width("64vp")
.height("64vp")
Text("包厢")
.height("32vp")
.textAlign(TextAlign.Center)
.fontSize("16fp")
.fontWeight(FontWeight.Bold)
}
}
.width("100%")
.padding({ top: 10, bottom: 10, left: 20, right: 20 })
.justifyContent(FlexAlign.SpaceBetween)
}
}
再将分类列表组件引入到首页中,代码如下:
import Header from '../components/Header'
import CarouseMap from '../components/Index/CarouselMap'
import CategoryList from '../components/Index/CategoryList'
@Entry
@Component
struct Index {
@State keyword: string = ''
build() {
Row() {
Column() {
Tabs({
barPosition: BarPosition.End
}){
TabContent(){
// 增加Column原因是因为TabContent中只有一个子组件,否则会报错
Column(){
Header({onSearchChange: this.onSearchChange.bind(this)}) // 顶部导航
CarouseMap() // 轮播图
CategoryList() // 分类列表
}.width('100%').height('100%').alignItems(HorizontalAlign.Start)
}.tabBar({icon: $rawfile('u48.png'), text: '首页'})
TabContent(){
Text('待开发中...')
}.tabBar({icon: $rawfile('u43.png'), text: '订单'})
TabContent(){
Text('待开发中...')
}.tabBar({icon: $rawfile('u53.png'), text: '我的'})
}
.barMode(BarMode.Fixed)
.barHeight(60)
}
.width('100%')
}
.height('100%').alignItems(VerticalAlign.Top)
}
/**
* 搜索内容改变事件
* @param data
*/
onSearchChange(data: string){
console.log('search value:', data)
}
}
页面效果如下图:
3.6 自定义构建函数 - 产品列表
最后,再创建一个产品列表,在components/Index目录中创建ProductList组件。
3.6.1 visual低代码编辑
这部分样式结构会稍微复杂点,所以在visual文件的低代码模式下,先画出基本轮廓和第一个产品信息,其他等转换为代码模式时,通过列表循环输出即可。如下图:
3.6.2 转换为代码
转换后的代码,只是基本结构和部分产品样式,代码如下:
@Preview
@Component
export default struct ProductList {
build() {
Row({ space: "0vp" }) {
Column() {
Text("精品特卖")
.width("100%")
.height("50vp")
.fontSize("16fp")
.fontWeight(FontWeight.Bold)
Row() {
Row() {
Column() {
Column() {
Image($rawfile('u62.png'))
.width("150vp")
.height("65vp")
Text("啫啫鱼头")
.width("150vp")
.height("26vp")
.textOverflow({ overflow: TextOverflow.Ellipsis })
.fontSize("10fp")
Row() {
Text("¥52")
.width("60vp")
.height("30vp")
.fontColor("#ff0000")
.fontSize("12fp")
.fontWeight(FontWeight.Bold)
Text("原价:55元")
.width("90vp")
.height("30vp")
.fontColor("#575757")
.decoration({ type: TextDecorationType.LineThrough, color: "#313030" })
}
.width("150vp")
}
}
Column()
}
}
Row() {
Column()
Column()
}
}
.width("100%")
}
.width("100%")
.padding({ top: "10vp", bottom: "10vp", left: "15vp", right: "15vp" })
}
}
此时将其引入到首页中效果是这样的,如下图:
产品信息是通过接口获取的列表数据,然后通过GridRow组件和GridCol组件组合使用,将产品渲染成两列。这里暂时未调用接口,所以先将数据定义在组件中,通过ForEach遍历数组。
components/Index/ProductList.ets代码如下:
type ProductType = {
title: string
thumb: Resource
price: number
originPrice: number
}
@Preview
@Component
export default struct ProductList {
@State productList: Array<ProductType> = [
{ title: '啫啫鱼头', thumb: $rawfile('u62.png'), price: 52, originPrice: 55 },
{ title: '黑椒汁啫牛肉', thumb: $rawfile('u76.png'), price: 55, originPrice: 60 },
{ title: '啫啫田鸡', thumb: $rawfile('u90.png'), price: 52, originPrice: 55 },
{ title: '咸蛋黄啫苦瓜', thumb: $rawfile('u104.png'), price: 26, originPrice: 30 }
]
// 定义内联构建函数
@Builder ImageItems(item: ProductType){
Row(){
Column(){
Image(item.thumb)
.width("100%")
.height("100vp")
.borderRadius(8)
Text(item.title)
.width("100%")
.height("36vp")
.textOverflow({ overflow: TextOverflow.Ellipsis })
.fontSize("18fp")
Row() {
Text("¥" + item.price)
.width("40%")
.fontColor("#ff0000")
.fontSize("18fp")
.fontWeight(FontWeight.Bold)
Text("原价:" + item.originPrice + "元")
.width("60%")
.fontColor("#575757")
.fontSize("12fp")
.decoration({ type: TextDecorationType.LineThrough, color: "#313030" })
}
.width("100%").alignItems(VerticalAlign.Center)
}
}.width('100%').padding(10)
}
build() {
Row({ space: "0vp" }) {
Column() {
Text("精品特卖")
.width("100%")
.height("50vp")
.fontSize("16fp")
.fontWeight(FontWeight.Bold)
.padding(10)
// 每行分两列展示
GridRow({columns: 2}){
ForEach(this.productList, (item: ProductType, index) => {
GridCol(){
this.ImageItems(item)
}
})
}
}.padding(5)
}
.width("100%")
}
}
此时首页就已完成了,效果如下图:
3.6.3 滚动条
不过此时,大家可能会发现内容超出来屏幕可见区域,无法上拉显示。这个问题比较简单,可以使用List和ListItem组件解决。
pages/index.ets最终代码如下:
import Header from '../components/Header'
import CarouseMap from '../components/Index/CarouselMap'
import CategoryList from '../components/Index/CategoryList'
import ProductList from '../components/Index/ProductList'
@Entry
@Component
struct Index {
@State keyword: string = ''
build() {
Row() {
Column() {
Tabs({
barPosition: BarPosition.End
}){
TabContent(){
// 增加Column原因是因为TabContent中只有一个子组件,否则会报错
Column(){
Header({onSearchChange: this.onSearchChange.bind(this)}) // 顶部导航
List(){
ListItem(){
Column(){
CarouseMap() // 轮播图
CategoryList() // 分类列表
ProductList() // 产品列表
}
}
}.layoutWeight(1)
}.width('100%').height('100%').alignItems(HorizontalAlign.Start)
}.tabBar({icon: $rawfile('u48.png'), text: '首页'})
TabContent(){
Text('待开发中...')
}.tabBar({icon: $rawfile('u43.png'), text: '订单'})
TabContent(){
Text('待开发中...')
}.tabBar({icon: $rawfile('u53.png'), text: '我的'})
}
.barMode(BarMode.Fixed)
.barHeight(60)
}
.width('100%')
}
.height('100%').alignItems(VerticalAlign.Top)
}
/**
* 搜索内容改变事件
* @param data
*/
onSearchChange(data: string){
console.log('search value:', data)
}
}
此时页面超出部分则可以上拉查看了,如下图:
这就是首页全部静态代码了,后期须根据接口数据动态渲染及增加交互功能,这期就不讲解了。希望对大家有所帮助~