赞
踩
工具人小白带来项目-简易购物车
开发工具:DevEco Studio
图标:HarmonyOS图标库和阿里巴巴矢量图标库
一、登录界面
使用了Ohos前端框架的登录页面组件,它采用了类似于React的组件化开发模式。
来浅浅分析一下这段代码:
1.import语句引入了三个Ohos模块:@ohos.router、@ohos.prompt和@ohos.promptAction。这些模块提供了路由导航、提示框和动作列表等常用功能。
2.定义了一个名为LoginPage的组件,使用了@Entry和@Component装饰器,表明这是一个入口组件和普通组件。
3.在LoginPage组件内部,定义了两个状态变量@State password和@State username,用于存储密码和用户名。即@State注解定义了两个状态变量:password和username,它们被初始化为字符串类型的空字符串。这些状态变量可以在组件中被修改,并且当它们的值被修改时,页面会自动重新渲染。
4.build()方法用于构建组件树。在build方法中,使用了Column、Row、Text、Image、TextInput、Divider和Button等控件,构建了登录页面的UI布局和交互逻辑。首先创建一个Column组件,并设置其样式为居中对齐。然后在Column组件中添加了多个子组件,包括标题、用户名输入框、密码输入框和登录按钮。在用户名和密码输入框中,使用了Image组件来显示图标,使用了TextInput组件来获取用户输入。当用户输入改变时,使用onChange方法更新username和password状态变量的值。
5.在Button的onClick事件处理函数中,根据输入的用户名和密码进行判断,如果匹配成功则跳转到"pages/mainUI"页面,否则显示密码或用户名错误的提示消息。如果用户名和密码均为"admin",则使用router.replaceUrl()函数将页面导航到pages/mainUI页面;否则,使用promptAction.showToast()函数显示一个提示框,提示用户输入的用户名或密码错误。
总体上看,这段代码实现了一个简单的登录页面,包括了UI布局和基本的交互逻辑。它使用了Ohos前端框架提供的组件和状态管理功能,以及路由和提示消息的操作。
代码如下:
- import router from '@ohos.router'
- import prompt from '@ohos.prompt'
- import promptAction from '@ohos.promptAction'
-
- @Entry
- @Component
- struct LoginPage {
- @State password: string = ''
- @State username: string = ''
-
- build() {
-
- Column() {
- Text("登陆")
- .fontSize(50)
- .fontWeight(FontWeight.Bold).margin({
- bottom: 60
- })
- Row() {
- Text("用户名")
- .fontSize(18)
- .fontWeight(FontWeight.Bold)
- }.width("100%")
-
- Row() {
- Image($r("app.media.ic_user_portrait")).width(30)
-
- TextInput({
- placeholder: "请输入用户名"
- }).width(200).onChange((val: string) => {
- this.username = val
-
-
- })
- }.margin({
- bottom: 8,
- top: 8
- }).width("100%")
-
- Divider().strokeWidth(4)
-
- Row() {
- Text("密码")
- .fontSize(18)
- .fontWeight(FontWeight.Bold).margin({
- bottom: 8,
- top: 8
- })
- }.width("100%")
-
- Row() {
- Image($r("app.media.ic_public_lock")).width(30)
-
- TextInput({
- placeholder: "请输入密码"
- }).width(200).onChange((val: string) => {
- this.password = val
-
-
- }).type(InputType.Password)
- }.width("100%")
-
- Divider().strokeWidth(4)
-
- Button("登陆").width("90%").height(60).backgroundColor(Color.Orange).onClick(() => {
-
- if (this.username == "admin" && this.password == "admin") {
-
- router.replaceUrl({
- url: "pages/mainUI"
- })
- }
- else {
- promptAction.showToast({
- message:"密码或用户名错误,请重新输入"
- })
- }
- })
- }
- .width('100%')
- .height('100%')
- .justifyContent(FlexAlign.Center)
- .alignItems(HorizontalAlign.Center)
- .padding({
- left: 20,
- right: 20
- })
- }
- }

二、购物界面
1.实现了一个简单的页面布局和交互设计,通过自定义的 tab 组件和 tabsController 实现了选项卡的切换和样式变化。具体如下: 定义了一个名为 Index 的结构体,用于创建页面的入口组件。Index 结构体包含了三个自定义的 tab 组件:tabHome、tabContactsGroup 和 tabMy。tabHome 组件用于创建“首页”tab,它是一个 Row 组件内嵌一个 Column 组件。在 Column 组件中,首先添加了一个上方的 Blank(填充)组件,然后根据当前 index 值判断是否显示一个绿色的图标,图标的样式通过 $r 方法获取。接下来是一个 Text 组件用于显示文本内容“首页”,字体大小为 16px,字体颜色根据 index 值来决定。最后,又添加了一个下方的 Blank(填充)组件。整个 tabHome 组件的点击事件处理逻辑是将 index 设置为 0,并调用 tabsController 的 changeIndex 方法来改变选项卡的索引值。tabContactsGroup 和 tabMy 组件的结构与 tabHome 类似,只是显示的图标、文本和点击事件处理不同。tabContactsGroup 组件对应“购物车”tab,显示了一个购物车的图标;tabMy 组件对应“我的”tab,显示了一个联系人的图标。最后,在 Index 结构体中,定义了一个私有的 tabsController 变量,并初始化为一个 TabsController 对象。同时,还定义了一个 @State 注解修饰的 index 变量,初始值为 0,用于记录当前选中的 tab 的索引。
2.接下来是几个自定义的选项卡组件,每个组件都包含一个图标和标题,并且可以根据当前选中的选项卡的下标来改变样式。然后,在build方法中,使用Tabs组件创建了一个选项卡容器。通过设置barPosition属性为BarPosition.End,将导航栏放在底部。TabContent组件中定义了每个选项卡的内容。在首页的选项卡中,展示了一些商品列表,每个商品都有名称、价格和一个“选购”按钮。点击“选购”按钮会触发addToCart方法,将商品添加到购物车中。在购物车的选项卡中,展示了购物车中的商品列表,每个商品都有一个复选框、名称和价格。点击复选框会触发toggleCheck方法,切换商品的选中状态。点击“结算”按钮会筛选出已选中的商品,并计算总价。在我的的选项卡中,展示了一些个人信息和功能选项,如基本信息、收藏、关注店铺、收货地址和设置。
- @Entry
- @Component
- struct Index {
- //#63bf02 绿色
- //索引从0开始
- private tabsController: TabsController = new TabsController();
- //选项卡的下标,从0开始,初始为0(表示被选中
- @State index: number = 0
- //自定义“首页”的tab组件
- @Builder tabHome() {
- Row() {
- Column() {
- //上填充
- Blank()
- Image(this.index == 0 && $r('app.media.ic_public_home'))
- .size({ width: 25, height: 25 })
- Text('首页').fontSize(16)
- .fontColor(this.index == 0 ? "#63bf02" : "#000000")
- //下填充
- Blank()
- }
- .height('100%')
- .width('100%')
- .onClick(() => {
- this.index = 0;
- this.tabsController.changeIndex(this.index);
- })
- }
- }
- //自定义“购物车”的tab组件
- @Builder tabContactsGroup() {
- Row() {
- Column() {
- //上填充
- Blank()
- Image(this.index == 1 && $r('app.media.gouwuche'))
- .size({ width: 25, height: 25 })
- Text('购物车').fontSize(16)
- .fontColor(this.index == 1 ? "#63bf02" : "#000000")
- //下填充
- Blank()
- }
- .height('100%')
- .width('100%')
- .onClick(() => {
- this.index = 1;
- this.tabsController.changeIndex(this.index);
- })
- }
- }
- //自定义“我的”的tab组件
- @Builder tabMy() {
- Row() {
- Column() {
- //上填充
- Blank()
- Image(this.index == 2 && $r('app.media.ic_public_contacts'))
- .size({ width: 25, height: 25 })
- Text('我的').fontSize(16)
- .fontColor(this.index == 2 ? "#63bf02" : "#000000")
- //下填充
- Blank()
- }
- .height('100%')
- .width('100%')
- .onClick(() => {
- this.index = 2;
- this.tabsController.changeIndex(this.index);
- })
- }
- }
-
- cartItems: {
- name: string;
- price: string;
- }[] = [];
- private cart: {
- name: string;
- price: string;
- select: boolean
- }[] = [];
- private total: number = 0;
- addToCart = (itemName: string, itemPrice: string) => {
- const newCart = [...this.cart, { name: itemName, price: itemPrice, select: false }];
- const newTotal = this.total + parseInt(itemPrice);
- this.cart = newCart;
- this.total = newTotal;
- };
- toggleCheck = (index: number) => {
- const updatedCart = [...this.cart];
- updatedCart[index].select = !updatedCart[index].select;
- this.cart = updatedCart;
- };
-
- build() {
- Row() {
- Column() {
- Tabs({
- index: 0,
- barPosition: BarPosition.End,
- controller: this.tabsController
- }) {
- //首页的tab绑定
- TabContent() {
- Row() {
- Column() {
- Text("商品列表")
- .fontSize(50)
- .fontWeight(FontWeight.Bold).margin({
- bottom: 60
- })
- Row() {
- Text("幸运咖啡")
- .fontSize(28)
- .fontWeight(FontWeight.Bold).margin({
- bottom: 8,
- top: 8,
- right: 90, //添加右边距
- })
- Text("价格:").fontSize(28)
- Text("26").fontSize(28)
-
- Button("选购").width("15%").height(30).backgroundColor(Color.Orange)
- .onClick(() => {
-
- const itemName = "幸运咖啡";
- const itemPrice = "26";
- this.addToCart(itemName, itemPrice);
- })
- }.width("100%")
-
- Row() {
- Text("拿铁")
- .fontSize(28)
- .fontWeight(FontWeight.Bold).margin({
- bottom: 8,
- top: 8,
- right: 150, //添加右边距
- })
- Text("价格:").fontSize(28)
- Text("16").fontSize(28)
- Button("选购").width("15%").height(30).backgroundColor(Color.Orange)
- .onClick(() => {
- const itemName = "拿铁";
- const itemPrice = "16";
- this.addToCart(itemName, itemPrice);
-
- })
- }.width("100%")
-
- Row() {
- Text("柠檬水")
- .fontSize(28)
- .fontWeight(FontWeight.Bold).margin({
- bottom: 8,
- top: 8,
- right: 120, //添加右边距
- })
- Text("价格:").fontSize(28)
- Text("4").fontSize(28).fontWeight(FontWeight.Bold).margin({
- right: 20, //添加右边距
- })
- Button("选购").width("15%").height(30).backgroundColor(Color.Orange)
- .onClick(() => {
- const itemName = "柠檬水";
- const itemPrice = "4";
- this.addToCart(itemName, itemPrice);
-
- })
- }.width("100%")
-
- Row() {
- Text("四季春果")
- .fontSize(28)
- .fontWeight(FontWeight.Bold).margin({
- bottom: 8,
- top: 8,
- right: 90, //添加右边距
- })
- Text("价格:").fontSize(28)
- Text("15").fontSize(28)
- Button("选购").width("15%").height(30).backgroundColor(Color.Orange).onClick(() => {
- const itemName = "四季春果";
- const itemPrice = "15";
- this.addToCart(itemName, itemPrice);
- })
- }.width("100%")
-
- Row() {
- Text("芋泥豆乳")
- .fontSize(28)
- .fontWeight(FontWeight.Bold).margin({
- bottom: 8,
- top: 8,
- right: 90, //添加右边距
- })
- Text("价格:").fontSize(28)
- Text("18").fontSize(28)
- Button("选购").width("15%").height(30).backgroundColor(Color.Orange)
- .onClick(() => {
- const itemName = "芋泥豆乳";
- const itemPrice = "18";
- this.addToCart(itemName, itemPrice);
-
- })
- }.width("100%")
-
- Row() {
- Text("益和烤奶")
- .fontSize(28)
- .fontWeight(FontWeight.Bold).margin({
- bottom: 8,
- top: 8,
- right: 90, //添加右边距
- })
- Text("价格:").fontSize(28)
- Text("16").fontSize(28)
- Button("选购").width("15%").height(30).backgroundColor(Color.Orange)
- .onClick(() => {
- const itemName = "益和烤奶";
- const itemPrice = "16";
- this.addToCart(itemName, itemPrice);
- })
- }.width("100%")
- }
- .width('100%')
- }
- .height('100%')
- }
- .tabBar(this.tabHome())
- //购物车的tab绑定
- TabContent() {
-
- Row() {
- Column() {
- Text("购物车")
- .textAlign(TextAlign.Center)
- .fontSize(50)
- .fontWeight(FontWeight.Bold).margin({
- bottom: 600,
-
-
- });
-
- this.cart.map((item, index) => {
- Row() {
- Checkbox()
- .select(item.select)
- .width(40)
- .height(40)
- .onClick(() => this.toggleCheck(index))
- Text(item.name.toString())
- .fontSize(28)
- .fontWeight("bold")
- .margin({ left: 10, right: 90 })
- Text(item.price.toString())
- .fontSize(28)
- .fontWeight("bold")
- .margin({ left: 10, right: 90 })
- }.width("100%")
- .height(60)
- .alignItems(VerticalAlign.Center)
- .padding({ top: 10 })
- }
- )
-
- Row() {
- Button("结算")
- .width("40%")
- .height(60)
- .backgroundColor(Color.Orange)
- .onClick(() => {
- const selectedItems = this.cart.filter(item => item.select);
- const selectedTotal = selectedItems.reduce((total, item) => total + parseInt(item.price), 0);
- console.log(JSON.stringify(selectedItems));
- console.log(String(selectedTotal));
- })
-
-
- Text(`合计:${this.total}`)
- .fontSize(28)
- .fontWeight(FontWeight.Bold);
- }.width("100%")
- }
- .width('100%')
- }
- .height('100%')
- }
- .tabBar(this.tabContactsGroup())
- //我的的tab绑定
- TabContent() {
- Row() {
- Column() {
- Text("我的")
- .fontSize(50)
- .fontWeight(FontWeight.Bold).margin({
- bottom: 60
- })
- Row() {
- Image($r("app.media.ic_user_portrait")).width(30)
- Text("基本信息")
- .fontSize(18)
- .fontWeight(FontWeight.Bold).margin({
- bottom: 8,
- top: 8
- })
- }.width("100%")
-
- Row() {
- Image($r("app.media.ic_public_favor")).width(30)
- Text("收藏")
- .fontSize(18)
- .fontWeight(FontWeight.Bold).margin({
- bottom: 8,
- top: 8
- })
- }.width("100%")
-
- Row() {
- Image($r("app.media.ic_public_highlight")).width(30)
- Text("关注店铺")
- .fontSize(18)
- .fontWeight(FontWeight.Bold).margin({
- bottom: 8,
- top: 8
- })
- }.width("100%")
-
- Row() {
- Image($r("app.media.shouhuodizhi")).width(30)
- Text("收货地址")
- .fontSize(18)
- .fontWeight(FontWeight.Bold)
- }.width("100%")
-
- Row() {
- Image($r("app.media.ic_public_settings")).width(30)
- Text("设置")
- .fontSize(18)
- .fontWeight(FontWeight.Bold)
- }.width("100%")
- }
- .width('100%')
- }
- .height('100%')
- }
- .tabBar(this.tabMy())
- }
- .scrollable(true)
- .width('100%')
- .height('100%')
- .barHeight(60)
- .barMode(BarMode.Fixed)
- //如果用户点了组件,就产生回调
- .onChange((index: number) => {
- this.index = index;
- })
- }
- .width('100%')
- }
- .height('100%')
- }
- }

但是在上述代码中有一段代码出现错误,如下:
- this.cart.map((item, index) => {
- Row() {
- Checkbox()
- .select(item.select)
- .width(40)
- .height(40)
- .onClick(() => this.toggleCheck(index))
- Text(item.name.toString())
- .fontSize(28)
- .fontWeight("bold")
- .margin({ left: 10, right: 90 })
- Text(item.price.toString())
- .fontSize(28)
- .fontWeight("bold")
- .margin({ left: 10, right: 90 })
- }.width("100%")
- .height(60)
- .alignItems(VerticalAlign.Center)
- .padding({ top: 10 })
- }
- )

经分析,this.state.cart.map(...)从逻辑上讲这段代码是正确的,但是还会报错,原因是这部分代码不符合UI组件的语法,this.cart 是一个数据数组,而不是一个 UI 组件。为了符合 UI 组件语法,我需要将 this.cart 的处理逻辑放在一个单独的函数或方法中,并在需要的地方调用该函数来生成 UI 组件。第一次尝试我建立了一个空的逻辑数组cartItems,将 this.cart.map 的结果存储在 cartItems 数组中,并使用 forEach 循环来渲染每个 Row 组件(如下代码)。
- const cartItems = this.cart.map((item, index) => {
- return Row() {
- Checkbox()
- .select(item.select)
- .width(40)
- .height(40)
- .onClick(() => this.toggleCheck(index))
- Text(item.name.toString())
- .fontSize(28)
- .fontWeight("bold")
- .margin({ left: 10, right: 90 })
- Text(item.price.toString())
- .fontSize(28)
- .fontWeight("bold")
- .margin({ left: 10, right: 90 })
- }.width("100%")
- .height(60)
- .alignItems(VerticalAlign.Center)
- .padding({ top: 10 })
- });
-
- cartItems.forEach((item) => {
- item;
- });

,
仍会报错并显示无法查询到Row()方法,之后有尝试另一种方法(如下代码)
- function generateCartItem(item, index) {
- return Row(){
- Checkbox()
- .select(item.select)
- .width(40)
- .height(40)
- .onClick(() => this.toggleCheck(index))
- Text(item.name.toString())
- .fontSize(28)
- .fontWeight("bold")
- .margin({ left: 10, right: 90 })
- Text(item.price.toString())
- .fontSize(28)
- .fontWeight("bold")
- .margin({ left: 10, right: 90 })
- }.width("100%")
- .height(60)
- .alignItems(VerticalAlign.Center)
- .padding({ top: 10 })
-
- // 在你的组件渲染方法中调用 generateCartItem 函数来生成 UI 组件
- render() {
- return (
- {this.cart.map((item, index) => generateCartItem(item, index))}
- );
- }

但仍旧报错,所以我处于知道错误的原因,但对解决的方法无法处理错误。
由于远程虚拟机一直显示被占用,运行结果由视图展示
三、不足之处
1.界面未进行美化,比较粗糙
2.无注册功能,用户唯一
3.功能简单,操作简单,复杂程度不够
四、改进之处
1.应增加注册功能,这样可以拥有多个界面,还应该增加商品详情界面,使功能复杂化
2.在商品列表中,应建立数组,将商品信息存储在数组中,以便简化商品列表界面商品信息显示的代码
3.增加复杂度,是代码内容更丰富
五、总结
通过学习这门课程以及此次代码编程,对鸿蒙有了初步了解,也学习到了新的知识,收获很大。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。