鸿蒙购物应用开发
目录
一、前言
我在这篇文章中分享了我的鸿蒙移动应用开发课程的期末大作业--鸿蒙购物应用BuyBuyBuy,它是我在学习了这一学期的课程之后利用方舟开发框架(简称ArkUI开发框架)所做的一些UI开发成果。我做的这个项目是一个UI页面,里面展示了一些假数据,并没有涉及到真正的网络数据交互。所有的数据都是存在本地的。虽然这个页面还没有真正和后台进行数据交互,但这个项目可以用来学习UI页面的开发。通过构建这个UI页面,我学到了很多关于ArkUI开发框架的知识,这对我今后的开发工作会有很大的帮助。虽然我的代码还有很多需要优化的地方,但我仍然想通过这篇文章展示我利用所学知识所完成的页面。ArkUI是一套用于构建HarmonyOS / OpenHarmony应用界面的声明式UI开发框架,它使用简单的UI信息语法、多样的UI组件以及实时界面语言工具,帮助开发者快速构建应用界面,提高开发效率。我希望能通过我的初次体验,让未接触过ArkUI的开发者能够更深入地了解和认识它。
二、概述
本项目将通过使用DevEco Studio软件,从零开始在移动设备上完成鸿蒙购物应用的编译。此次以手机为例。如果需要安装DevEco Studio,可以参考鸿蒙开发者联盟的安装教程。在本项目中,我们的目标是开发一个比较完整的鸿蒙购物应用。
三、项目开发
3.1 开发工具和框架
本次开发使用华为鸿蒙官网提供的开发工具HUAWEI DevEco Studio,并且采用ArkUI框架作为开发的主要框架。如图3.1-1、图3.1-2所示:
图 3.1-1
图 3.1-2
3.2 创建项目
在DevEco Studio下载安装成功后,打开DevEco Studio,选择左侧的“Create Project”。在下一个页面里,选择左侧的“Application”,然后选择“Empty Ability”,然后点击“Next”。在下一个页面里,把“Project name”改成“BuyWhat”,然后接着选择存储路径和SDK版本,本例中选择的是SDK版本9和ArkTS语言。最后点击“Finish”创建项目。如图3.2-1、图3.1-2所示:
图 3.2-1
图 3.2-2
3.3 创建虚拟设备
在创建虚拟设备时,我们需要先进行登录华为账号操作然后才能使用虚拟设备,在我们完成登录以后我们就可以选择我们所需要的虚拟设备了。如图3.3-1、图3.3-2、图3.3-3所示:
图 3.3-1
图 3.3-2
图 3.3-3
3.4 开发思路
这是我的鸿蒙期末大作业,通过一学期课程的学习,研究出来的一些成果,代码还有很多需要优化的地方,本文内容仅为利用学到的知识做的页面。ArkUI开发框架是方舟开发框架的简称。根据官方的资料,ArkUI是一套构建 HarmonyOS / OpenHarmony 应用界面的声明式UI开发框架,其使用极简的UI信息语法、丰富的UI组件以及实时界面语言工具,帮助开发者提升应用界面开发效率。对于未接触过ArkUI的开发者,不妨跟着笔者对ArkUI进行初次体验。我试着开发这个鸿蒙购物应用BuyBuyBuy主要是因为在学习了一学期的鸿蒙移动应用开发课程后,我想借着期末大作业来检验一下自己的这一学期的学习效果如何,于是我就尝试模仿现有的购物应用来实现其中比较简单的一部分功能。 我们在这个软件里主要实现了应用的启动页面,应用的隐私协议页面,应用的广告页面,应用的登录注册页面,以及购物应用的首页、商品分类、购物车、我的四个页面。我做的这个项目是一个UI页面,里面展示了一些假数据,并没有涉及到真正的网络数据交互。所有的数据都是存在本地的。虽然这个页面还没有真正和后台进行数据交互,但这个项目可以用来学习UI页面的开发。通过构建这个UI页面,我学到了很多关于ArkUI开发框架的知识,这对我今后的开发工作会有很大的帮助。虽然这个代码我已经修改了很多次,但我知道我的代码肯定还有很多需要优化的地方,希望大家对我的不足之处给予批评指正。
3.5 项目结构及部分功能演示
3.5.1 购物应用启动页面
实现购物应用启动页面
在应用启动页面会展示鸿蒙购物应用的logo、名称、以及启动页的背景图、启动页的宣传标语等等。在三秒钟后会通过路由自动跳转到带有应用隐私协议对话框的启动页面,在这里会展示关于隐私协议的一部分声明,如果用户想了解更多的隐私协议政策可以点击隐私协议保护声明,然后通过路由自动跳转到应用隐私协议页面,用户在这个页面可以详细地阅读相关的隐私协议政策。在用户阅读完相关的隐私政策后,用户可以点击同意按钮,跳转到应用广告页面,然后进行相关的操作,如果用户点击了不同意按钮,则无法跳转到下一个页面,进行相关的操作,也无法正常使用本应用。如图3.5.1-1、图3.5.1-2所示:
图 3.5.1-1
图 3.5.1-2
3.5.2 购物应用隐私协议页面
实现购物应用隐私协议页面
在隐私协议页面会详细地展示鸿蒙购物应用BuyBuyBuy的隐私协议政策的具体内容,用户在这个页面可以详细地阅读相关的隐私协议政策,了解鸿蒙购物应用BuyBuyBuy的隐私权限。在用户阅读完相关的隐私政策后,用户可以点击返回按钮,退回到带有应用隐私协议对话框的启动页面,然后进行相关的操作。如图3.5.2-1所示:
图 3.5.2-1
3.5.3 购物应用广告页面
实现购物应用广告页面
在应用广告页面会展示一所学校的广告,上面展示该学校的图片,下面展示该学校的简介,在五秒钟后会通过路由自动跳转到登录注册页面。如果用户不想等待,用户可以点击跳过按钮,直接跳转到登录注册页面,然后进行登录注册的相关操作,如果用户不点击跳过按钮,则五秒钟后会通过路由自动跳转到登录注册页面,然后用户便可以进行登录注册的相关操作。如图3.5.3-1所示:
图 3.5.3-1
3.5.4 购物应用登录注册页面
实现购物应用登录注册页面
在应用登录注册页面上面展示一张购物应用的背景图片,下面是用户的登录框,在用户的账号和密码输入为空时,应用会进行相应的提示,请输入账号,请输入密码;在用户的账号和密码输入错误时,应用会进行相应的提示,账号或密码错误请重新输入;在用户的账号和密码输入正确时,应用会进行相应的提示,登录成功,然后通过路由自动跳转到购物应用首页。如果用户没有账号,用户可以点击注册账号的按钮进行账号的注册。在用户注册账号时,如果用户的账号和密码输入为空,应用会进行相应的提示,请输入账号,请输入密码;在用户输入正确的账号和密码后,用户可以点击立即注册的按钮进行账号注册,在账号注册成功后,用户可以点击立即登录的按钮进行账号的登录。当然在页面的最下面,用户也可以选择第三方登录,QQ登录或者微信登录。如图3.5.4-1、图3.5.4-2所示:
图 3.5.4-1
图 3.5.4-2
3.5.5 购物应用首页页面
实现购物应用首页页面
在购物应用首页页面上面是商品的轮播图,中间是商品的菜单,下面是商品的列表,用户可以实现对商品的浏览,在点击商品时可以跳转到商品的详情页,查看商品的详情,在商品详情页,用户可以购物车,页面会自动跳转到购物应用购物车页面,在这个页面用户可以进行相关的操作。如图3.5.5-1、图3.5.5-2所示:
图 3.5.5-1
图 3.5.5-2
3.5.6 购物应用商品分类页面
实现购物应用商品分类页面
在购物应用商品分类页面最上面是商品的搜索框,下面的左边是商品的各种分类列表,下面的右边是商品的详细分类列表,用户在这个页面可以对商品进行浏览,查看商品的分类等操作。如图3.5.6-1所示:
图 3.5.6-1
3.5.7 购物应用购物车页面
实现购物应用购物车页面
在购物应用购物车页面最上面除了显示了购物车三个大字外,用户也可以对购物车进行清空操作。中间是商品列表,展示了用户加入购物车的商品的详情,如商品的名称、商品的型号、折扣价以及原价。在最下面用户可以对商品进行选择和不选择操作,进行计算商品的总价格的操作。如图3.5.7-1、图3.5.7-2所示:
图 3.5.7-1
图 3.5.7-2
3.5.8 购物应用我的页面
实现购物应用我的页面
在购物应用我的页面最上面显示了用户的头像、用户的昵称以及用户的会员等级,用户在这里可以查看自己账号的个人基本信息。中间是订单列表,展示了用户待付款的订单、待发货的订单、待收货的订单、待评价的订单以及进行退款或者售后的订单。然后下面展示了用户可以进行操作的全部订单、收货地址、收藏管理、商铺列表、更改密码、意见反馈的功能选项。如图3.5.8-1所示:
图 3.5.8-1
3.6 主要页面代码块
下面给出一些主要页面的代码块。
3.6.1 购物应用启动页面代码块
import promptAction from '@ohos.promptAction';
import router from '@ohos.router';
@Entry
@Component
struct LauncherPage1 {
@State countDownSeconds: number =3;
private timeId: number = 0;
onPageHide() {
router.clear();
clearInterval(this.timeId);
}
onPageShow() {
this.timeId = setInterval(() => {
if (this.countDownSeconds === 0) {
router.pushUrl({
url:'pages/LauncherPage2'
})
} else {
this.countDownSeconds--;
}
}, 1000);
}
build() {
Stack() {
//背景图
Image($r('app.media.ic_launcher_background')).width('100%').height('100%')
Column() {
Blank()
Image($r('app.media.ic_logo')).width(130).height(130).margin({ top: 460, bottom: 10 }).borderRadius(30)
Blank()
Text('BuyBuyBuy')
.fontSize(40)
.fontWeight(FontWeight.Bolder)
.fontStyle(FontStyle.Italic)
.fontColor(Color.Black)
Blank()
Text('购物总有新玩法')
.fontSize(25)
.fontColor(Color.White)
.margin({ top: 10 })
.fontStyle(FontStyle.Italic)
Blank()
}
}
}
}
3.6.2 购物应用隐私协议页面代码块
import promptAction from '@ohos.promptAction';
import router from '@ohos.router';
@Entry
@Component
struct PrivacyPage {
@State account: string = ''
@State password: string = ''
build() {
Column() {
Image($r("app.media.ic_login_background_top")).width('100%').height('25%')
Blank()
Column() {
Blank()
Text('隐私协议政策').fontSize(30).fontColor(Color.Black).fontWeight(FontWeight.Bolder)
Blank()
Text('本隐私声明描述了本声明的重要部分、服务如何处理您的个人信息、您如何行使数据主体权利、相关数据控制者以及如何联系数据控制者。如果隐私声明与本声明不一致,则以隐私声明为准。如果隐私声明中未指定,则以本声明为准。')
.fontSize(23)
.fontColor(Color.Black)
.fontWeight(FontWeight.Bold)
.margin({ left: 10, right: 10 })
Blank()
}
.width('95%')
.height('50%')
.backgroundColor(Color.White)
.padding({ left: 10, right: 10 })
.borderRadius(18)
.shadow({ radius: 20, color: (Color.Gray) })
Blank()
Button() {
Text('返回').fontColor(Color.White).fontSize(18)
}
.width('90%')
.height(50)
.backgroundColor(Color.Brown)
.onClick(() => {
router.back()
})
Blank()
}.width('100%').height('100%').backgroundColor(Color.White)
}
}
3.6.3 购物应用广告页面代码块
import router from '@ohos.router';
@Entry
@Component
struct AdvertisingPage {
@State countDownSeconds: number = 5;
private timeId: number = 0;
onPageHide() {
router.clear();
clearInterval(this.timeId);
}
onPageShow() {
this.timeId = setInterval(() => {
if (this.countDownSeconds === 0) {
this.jumpToLoginPage();
} else {
this.countDownSeconds--;
}
}, 1000);
}
jumpToLoginPage() {
router.pushUrl({
url: 'pages/LoginPage'
})
}
build() {
Stack() {
Column() {
Image($r("app.media.zut")).width('100%').height('28%')
Blank()
Column() {
Text('中原工学院').fontSize(26).fontColor(Color.Brown).fontWeight(FontWeight.Medium).margin({ top: 20 })
Text(' 中原工学院(Zhongyuan University of Technology)位于河南省郑州市,中原工学院是一所以工为主,以电子信息和纺织服装为特色优势,工、管、艺、理、文、经、法、哲等多学科协调发展的河南省特色骨干学科建设高校。是第二批深化创新创业教育改革示范高校,中俄交通大学联盟成员,河南省高校交通教育联盟理事长单位。2018年,学校被河南省确定为博士学位授予立项建设单位。学校始建于1955年,前身是榆次纺织机械工业学校;1957年迁往郑州市,更名郑州纺织机械制造学校;此后经历了河南纺织机械学院、郑州纺织机械学院、郑州纺织机电学校、郑州纺织机械配件厂、郑州纺织机电专科学校等阶段。1987年升格本科,定名郑州纺织工学院,隶属于原国家纺织工业部,1998年划转河南省管理,2000年更名为中原工学院。截至2023年4月,学校有龙湖校区、中原校区和西校区3个教学区,占地面积1610亩;设有21个教学院(部),开设71个本科专业,有全日制在校生2.2万余人,教职工1942人,其中专任教师1386人;拥有13个一级学科硕士学位授权点,60个二级学科硕士学位授权点,17个硕士专业学位授权类别。')
.fontWeight(FontWeight.Medium)
}
.width('95%')
.height('65%')
.backgroundColor(Color.White)
.padding({ left: 10, right: 10 })
.borderRadius(18)
.shadow({ radius: 20, color: (Color.Gray) })
Blank()
}.width('100%').height('100%').backgroundColor(Color.White)
Text('跳过广告' + this.countDownSeconds)
.fontColor(Color.White)
.fontSize(15)
.letterSpacing(0.05)
.backgroundColor('#33000000')
.border({
radius: 18,
width: 1,
color: Color.White
})
.padding({
left: 12,
right: 12,
top: 8,
bottom: 8
})
.margin({
bottom: 700,
left: 250,
})
.onClick(() => {
this.jumpToLoginPage();
})
}
}
}
3.6.4 购物应用登录注册页面代码块
3.6.4.1 登录页面
import promptAction from '@ohos.promptAction';
import router from '@ohos.router';
@Component
export struct LoginView {
@State account: string = ''
@State password: string = ''
build() {
Column() {
Image($r("app.media.ic_login_background_top")).width('100%').height('25%')
Blank()
Column() {
Blank()
Text('欢迎登录').fontSize(26).fontColor(Color.Brown).fontWeight(FontWeight.Medium)
Blank()
TextInput({ placeholder: '请输入账号' })
.width('100%')
.height(50)
.onChange((value: string) => {
this.account = value
})
.shadow({ radius: 10, color: Color.Gray })
TextInput({ placeholder: '请输入密码' })
.width('100%')
.height(50)
.type(InputType.Password)
.margin({ top: 12 })
.onChange((value: string) => {
this.password = value
})
.shadow({ radius: 10, color: Color.Gray })
Button() {
Text('立即登录').fontColor(Color.White).fontSize(18)
}
.width('100%')
.height(50)
.backgroundColor(Color.Brown)
.margin({ top: 20 })
.onClick(() => {
if (this.account === '') {
promptAction.showToast({ message: '请输入账号' })
return
}
if (this.password === '') {
promptAction.showToast({ message: '请输入密码' })
return
}
if (this.account === 'zut123456' && this.password === '123456') {
promptAction.showToast({ message: '登录成功',duration:2000 })
router.pushUrl({ url: 'pages/MainFrame' })
}
else {
promptAction.showToast({ message: '账号或密码错误,请重新输入' })
}
})
Blank()
Row() {
Text('注册账号').fontColor(Color.Brown).onClick(() => {
router.pushUrl({ url: 'pages/RegisterPage' })
})
Blank()
Text('忘记密码?').fontColor(Color.Brown)
}.width('100%')
Blank()
}
.width('95%')
.height('45%')
.backgroundColor(Color.White)
.padding({ left: 10, right: 10 })
.borderRadius(18)
.shadow({ radius: 20, color: (Color.Gray) })
Blank()
Row() {
Blank()
Divider().width(100).color(Color.Gray).strokeWidth(2)
Text('第三方登录').margin({ left: 10, right: 10 }).fontColor(Color.Brown).fontSize(16)
Divider().width(100).color(Color.Gray).strokeWidth(2)
Blank()
}
Row() {
Blank()
Image($r("app.media.ic_qq"))
.width(60)
.height(60)
.borderRadius(100)
.shadow({ radius: 10, color: Color.Gray })
.onClick(() => {
router.pushUrl({
url: 'pages/MainFrame'
})
})
Blank()
Image($r("app.media.ic_wechat"))
.width(60)
.height(60)
.borderRadius(100)
.shadow({ radius: 10, color: Color.Gray })
.onClick(() => {
router.pushUrl({
url: 'pages/MainFrame'
})
})
Blank()
}.width('100%').margin({ top: 10 })
Blank()
}.width('100%').height('100%').backgroundColor(Color.White)
}
}
3.6.4.2 注册页面
import promptAction from '@ohos.promptAction';
import router from '@ohos.router';
@Component
export struct RegisterView {
@State account: string = ''
@State password: string = ''
build() {
Column() {
Image($r("app.media.ic_login_background_top")).width('100%').height('25%')
Blank()
Column() {
Blank()
Text('注册账号').fontSize(26).fontColor(Color.Brown).fontWeight(FontWeight.Medium)
Blank()
TextInput({ placeholder: '请输入账号' })
.width('100%')
.height(50)
.type(InputType.Email)
.onChange((value: string) => {
this.account = value
})
.shadow({ radius: 10, color: Color.Gray })
TextInput({ placeholder: '请输入密码' })
.width('100%')
.height(50)
.type(InputType.Password)
.margin({ top: 12 })
.onChange((value: string) => {
this.password = value
})
.shadow({ radius: 10, color: Color.Gray })
Button() {
Text('立即注册').fontColor(Color.White).fontSize(18)
}
.width('100%')
.height(50)
.backgroundColor(Color.Brown)
.margin({ top: 20 })
.onClick(() => {
if (this.account === '') {
promptAction.showToast({ message: '请输入账号' })
return
}
if (this.password === '') {
promptAction.showToast({ message: '请输入密码' })
return
}
else {
promptAction.showToast({ message: '注册成功' })
}
})
Blank()
Row() {
Text('立即登录').fontColor(Color.Brown).onClick(() => {
router.back()
})
Blank()
Text('忘记密码?').fontColor(Color.Brown)
}.width('100%')
Blank()
}
.width('95%')
.height('50%')
.backgroundColor(Color.White)
.padding({ left: 10, right: 10 })
.borderRadius(18)
.shadow({ radius: 20, color: (Color.Gray) })
Blank()
}.width('100%').height('100%').backgroundColor(Color.White)
}
}
3.6.5 购物应用我的页面代码块
@Component
export struct MyComponent {
@Builder OrderItem(icon: Resource, name: string) {
Column() {
Image(icon).width(30).height(30)
Text(name).margin({ top: 3 }).fontSize(11)
}.layoutWeight(1)
}
@Builder MyItem(icon: Resource, name: string, isShowLine = true) {
Column() {
Row() {
Image(icon).width(20).height(20)
Text(name).fontSize(14).margin({ left: 10 })
Blank()
Image($r('app.media.arrow')).width(12).height(18)
}.width('100%').padding(15)
if (isShowLine) {
Divider().color('#e3e2e2').margin({ left: 15, right: 15 })
}
}
}
build() {
Column() {
Row() {
Image($r('app.media.user'))
.width(80)
.height(80)
.border({ radius: 50, width: 1, color: '#ffafafaf' })
.borderRadius(50)
.borderWidth(1)
Blank()
Column({ space: 3 }) {
Text('麻辣烫小公主').fontSize(18).fontColor('#ff2c2c2c')
Blank()
Row() {
Image($r('app.media.vip')).width(15).height(15)
Text('会员VIII级').fontSize(12).fontColor('#fff4c42c')
}
}.margin({ top: 3 })
Blank()
Image($r('app.media.arrow')).width(25).height(25)
Blank()
}
.width('100%')
.height(150)
.backgroundColor(Color.White)
.justifyContent(FlexAlign.Center)
.padding({ left: 20, right: 20 })
Column() {
Row() {
this.OrderItem($r('app.media.dfk'), '待付款')
this.OrderItem($r('app.media.dfh'), '待发货')
this.OrderItem($r('app.media.dsh'), '待收货')
this.OrderItem($r('app.media.dpj'), '待评价')
this.OrderItem($r('app.media.tksh'), '退款/售后')
}.width('100%').backgroundColor(Color.White)
.borderRadius(10).padding({ top: 20, bottom: 20 })
Column() {
this.MyItem($r('app.media.qbdd'), '全部订单')
this.MyItem($r('app.media.dzgl'), '收货地址');
this.MyItem($r('app.media.wdsc'), '收藏管理');
this.MyItem($r('app.media.spgl'), '商铺列表');
this.MyItem($r('app.media.zhaq'), '更改密码');
this.MyItem($r('app.media.yjfk'), '意见反馈', false);
}
.width('100%')
.backgroundColor(Color.White)
.borderRadius(10)
.margin({ top: 10 })
.padding({ top: 20, bottom: 20 })
}.width('100%').padding(10)
}.width('100%').height('100%')
.backgroundColor('#F4F4F4')
}
}
四、总结
通过这一段时间的努力,我最终完成了我的期末大作业,也比较好的实现了我最初构想的购物应用的功能。这次期末大作业也让我有了很多的收获,其中的一个收获就是:我们一定要敢于去尝试一些自己不太熟悉的事物,也许我们刚开始不会做的很厉害,也许我们也会经历许多失败,但是这些并重要,重要的是我们从失败和尝试当中一定会收获很多新的东西,在最终取得成功后会感受到不一样的快乐。比如这次通过点击跳过按钮实现跳过广告,刚开始尝试了好多次都没成功,甚至有了放弃使用跳过按钮的想法,但我最终并没有放弃我最初的想法,最后通过在网上查阅一些相关的资料成功的解决了这个问题,成功地实现了通过点击跳过按钮实现跳过广告的功能。当然也发现了自己的许多不足之处,那就是对ArkTS的知识了解还是太少,有许多地方还是明显感到比较生疏,理解的不够彻底。
总之,当我们开始去做一件事时,千万不要刚开始就去胆怯它,害怕它,我们要知道千里之行始于足下,一旦当你迈出了第一步之后,你就会惊奇的发现问题并没有你所想象的那么难做。在以后的学习中,我肯定还会遇到许多其他的难题,希望在解决其他难题时我都能像这次点击跳过按钮实现跳过广告一样,不轻言放弃,坚持到底,积极寻找解决难题的方法,直到彻底解决这个难题为止。
五、 源码下载