# BuyWhatDemo **Repository Path**: dot_happydz_admin/BuyWhatDemo ## Basic Information - **Project Name**: BuyWhatDemo - **Description**: 《买什么》电商Demo,使用哪个HarmonyOS API=8 ArkUI(TS)声明式开发。 - **Primary Language**: Unknown - **License**: Apache-2.0 - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2024-02-25 - **Last Updated**: 2024-02-25 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # ArkUI(TS)电商项目实战:买什么 ### 项目介绍 本项目基于[HarmonyOS](https://developer.harmonyos.com/cn/docs/documentation/doc-guides-V3/start-overview-0000001380121578-V3)的ArkUI框架TS扩展的声明式开发范式,关于语法和概念直接看官网官方文档地址:[基于TS扩展的声明式开发范式](https://developer.harmonyos.com/cn/docs/documentation/doc-guides-V3/arkts-get-started-0000001430600477-V3), **工具版本:** DevEco Studio 3.1 Canary1 **SDK版本:** 3.1.9.7(API Version 8 Release) ### 效果演示 ![](image/demo.gif) ### 页面解析 #### 主框架 使用容器组件:[Tabs](https://developer.harmonyos.com/cn/docs/documentation/doc-references-V3/ts-container-tabs-0000001380281306-V3?catalogVersion=V3) 、[TabContent](https://developer.harmonyos.com/cn/docs/documentation/doc-references-V3/ts-container-tabcontent-0000001430600669-V3?catalogVersion=V3)、作为主框架,底部Tab使用自定义布局样式,设置点击事件,每次点击更换选中的索引。来切换内容页面。 使用自定义组件来实现:首页、分类、购物车、我的的搭建。 ```javascript @Entry @Component struct MainFrame { @State selectIndex: number = 0 private controller: TabsController = new TabsController() private tabBar = getTabBarList() // 内容 @Builder Content() { Tabs({ controller: this.controller }) { TabContent() {HomeComponent()} TabContent() {ClassifyComponent()} TabContent() {ShoppingCartComponent({ isShowLeft: false })} TabContent() {MyComponent()} } .width('100%') .height(0) .animationDuration(0) .layoutWeight(1) .scrollable(false) .barWidth(0) .barHeight(0) } // 底部导航 @Builder TabBar() { Row() { ForEach(this.tabBar, (item: TabBarModel, index) => { Column() { Image(this.selectIndex === index ? item.iconSelected : item.icon) .width(23).height(23).margin({ right: index === 2 ? 3 : 0 }) Text(item.name) .fontColor(this.selectIndex === index ? '#dc1c22' : '#000000') .margin({ left: 1, top: 2 }) }.layoutWeight(1) .onClick(() => { this.selectIndex = index this.controller.changeIndex(index) }) }, item => item.name) }.width('100%').height(50) .shadow({ radius: 1, color: '#e3e2e2', offsetY: -1 }) } build() { Column() { this.Content() this.TabBar() }.width('100%').height('100%') } } ``` #### 首页 因为顶部标题栏需要浮在内容之上,所以根布局使用[容器组件Stack](https://developer.harmonyos.com/cn/docs/documentation/doc-references-V3/ts-container-stack-0000001430440709-V3?catalogVersion=V3),内容布局最外层使用[容器组件Scroll](https://developer.harmonyos.com/cn/docs/documentation/doc-references-V3/ts-container-scroll-0000001380600878-V3?catalogVersion=V3) 来实现滑动,内容整体分为3部分: 1. 轮播图:使用[容器组件Swiper](https://developer.harmonyos.com/cn/docs/documentation/doc-references-V3/ts-container-swiper-0000001380121774-V3?catalogVersion=V3) 2. 菜单:使用[容器组件Flex](https://developer.harmonyos.com/cn/docs/documentation/doc-references-V3/ts-container-flex-0000001430320949-V3?catalogVersion=V3)、默认横向布局,子布局宽度分为五等分,设置Flex的参数:wrap: FlexWrap.Wrap可实现自动换行。 3. 商品列表:和菜单的实现方式一样,只不过宽度是二等分 **向下滑动时标题栏显示功能**:使用 容器组件Scroll 的属性方法:onScroll来判断y轴方向的偏移量,根据偏移量计算出比例,改变标题栏的透明度。 ![](image/demo1.gif) **(以下是部分代码)** ```javascript @Component export struct HomeComponent { // 滑动的y偏移量 private yTotalOffset = 0 // 标题栏透明度 @State titleBarOpacity: number = 0 // 轮播图列表 private banners = [ $r("app.media.banner1"), $r("app.media.banner2"), $r("app.media.banner3"), $r("app.media.banner4"), $r("app.media.banner5"), $r("app.media.banner6"), ] // 菜单列表 private menuList = getHomeMenuList() // 商品列表 private goodsList: Array = getHomeGoodsList() // 轮播图 @Builder Banner() {...} // 菜单 @Builder Menu() {....} // 商品列表 @Builder GoodsList() {...} build() { Stack({ alignContent: Alignment.Top }) { Scroll() { Column() { this.Banner() this.Menu() this.GoodsList() }.backgroundColor(Color.White) }.scrollBar(BarState.Off) .onScroll((xOffset, yOffset) => { this.yTotalOffset += yOffset const yTotalOffsetVP = px2vp(this.yTotalOffset) // 轮播图高度 350 const scale = yTotalOffsetVP / 200 this.titleBarOpacity = scale }) Row(){ TitleBar({ title: '首页', isShowLeft: false, isShowRight: true, rightImage: $r('app.media.search') }) }.opacity(this.titleBarOpacity) }.width('100%').height('100%') } } ``` #### 详情页 和首页类似,根布局使用容器组件Stack,内容布局最外层使用容器组件Scroll 来实现滑动。因为详情页有相同布局,使用装饰器@Builder来抽离公共布局。 **向下滑动时标题栏显示功能原理和首页一样** ![](image/demo2.gif) **(以下是部分代码)** ```javascript import router from '@ohos.router'; @Entry @Component struct GoodsDetail { .... // 轮播图 @Builder Banner() {...} // 内容item @Builder ContentItem(title: string, content: string, top=1) { Row() { Text(title).fontSize(13).fontColor('#808080') Text(content).fontSize(13).margin({ left: 10 }).fontColor('#ff323232') Blank() Image($r('app.media.arrow')).width(12).height(18) }.width('100%').padding(13) .backgroundColor(Color.White).margin({ top: top }) } // 内容 @Builder Content() { ... this.ContentItem('邮费', '满80包邮', 7) this.ContentItem('优惠', '减5元') this.ContentItem('规格', '山核桃坚果曲奇; x3', 7) this.ContentItem('配送', '北京市朝阳区大塔路33号') } // 评论 @Builder Comment() {...} // 商品参数item @Builder GoodsParamItem(title: string, content: string) { Row() { Text(title).width(100).fontSize(13).fontColor('#808080') Text(content).fontSize(13).fontColor('#ff323232') .layoutWeight(1).padding({ right: 20 }) }.width('100%').padding({ top: 15 }) .alignItems(VerticalAlign.Top) } // 商品参数 @Builder GoodsParam() {...} build() { Stack() { Scroll() { Column() { this.Banner() this.Content() this.Comment() this.GoodsParam() } }.scrollBar(BarState.Off) .onScroll((xOffset, yOffset) => { this.yTotalOffset += yOffset const yTotalOffsetVP = px2vp(this.yTotalOffset) // 轮播图高度 350 const scale = yTotalOffsetVP / 350 this.titleBarBgTmOpacity = 1 - scale if (scale > 0.4) { this.titleBarBgWhiteOpacity = scale - 0.2 } else { this.titleBarBgWhiteOpacity = 0 } }) this.TopBottomOperateBar() }.width('100%').height('100%') .backgroundColor('#F4F4F4') } } ``` #### 分类 此页面很简单,就是左右两个[容器组件List](https://developer.harmonyos.com/cn/docs/documentation/doc-references-V3/ts-container-list-0000001430161125-V3?catalogVersion=V3)、一个宽度30%,一个宽度70%,使用[循环渲染组件ForEach](https://developer.harmonyos.com/cn/docs/documentation/doc-guides-V3/arkts-rendering-control-0000001430600481-V3?catalogVersion=V3)来渲染列表。 **(以下是部分代码)** ```javascript @Component export struct ClassifyComponent { // 搜索 @Builder Search() {...} // 内容 @Builder Content() { Row() { // 左分类 List() {...}.width('30%') // 右分类 List({scroller:this.scroller}) {...}.width('70%') }.width('100%').layoutWeight(1) .alignItems(VerticalAlign.Top) } build() { Column() { this.Search() this.Content() }.width('100%').height('100%') .backgroundColor(Color.White) } } ``` #### 购物车 此界面功能:全选、单选、删除商品、更改商品的数量。当有商品选中,右上角删除按钮显示。更改商品数量同步更新合计的总价格。 使用List组件来实现列表。数据数组使用装饰器@State定义。**数据更新必须是更改数组的项**。 ![](image/demo3.gif) **(以下是部分代码)** ```javascript @Component export struct ShoppingCartComponent { ... // 商品详情 @Builder GoodsItem(item: ShoppingCartModel, index: number) {...} build() { Column() { TitleBar() if (this.list.length > 0) { // 列表 List() {...}.layoutWeight(1) // 结算布局 Row() {...} } else { Row() { Text('空空如也~') } } }.width('100%').height('100%') .backgroundColor('#F4F4F4') } selectOperation(item: ShoppingCartModel, index: number) { // 替换元素,才能更改数组,这样页面才能更新 let newItem = {...item} newItem.isSelected = !item.isSelected this.list.splice(index, 1, newItem) this.calculateTotalPrice() } // 加/减操作 addOrSubtractOperation(item: ShoppingCartModel, index: number, type: -1 | 1) {...} // 计算合计 calculateTotalPrice() { let total = 0 let selectedCount = 0 for (const item of this.list) { if (item.isSelected) { selectedCount++ total += item.newPrice * item.count } } this.totalPrice = total this.isAllSelected = selectedCount === this.list.length } } ``` ### 结尾 此项目没有比较难的点,都是照着官方文档,查阅API做出来的,依靠着声明式UI的简洁和强大,页面搭建效率很高。 项目地址:[https://gitee.com/liangdidi/BuyWhatDemo](https://gitee.com/liangdidi/BuyWhatDemo) 每天进步一点点、需要付出努力亿点点。