HarmonyOS鸿蒙项目实战

[复制链接]
发表于 2026-1-31 16:11:11 | 显示全部楼层 |阅读模式
1.源码地点

源码地点:https://gitee.com/lyj-bc-cn/harmonyos_demo.git
devecostudio汗青版本:DevEco Studio汗青版本下载-华为开辟者同盟 (huawei.com)
此项目所用的情况为api9、DevEco Studio 3.1.1 Release,在api10以上的deveco studio中会无法运行而且爆出一堆错误。如果你使用的是DevEco Studio NEXT Developer Beta1或更高的版本,那照旧自求多福吧,由于bug是真的多啊。发起运行从堆栈下载的代码的时间使用较低的汗青版本编译器,但我头铁,就要死磕ArkTS API10 !!!
关于新版本的语法优化可以参考这篇文章:https://blog.csdn.net/u010274449/article/details/135618012
https://blog.csdn.net/u010274449/article/details/135618012
2.结果图

现在已完成的结果图如下,但由于我用的最新版的编译器,以是数据渲染的部门会有bug。现在展示部门结果,等改完了bug再更一篇,堆栈的源码随后奉上。

3.部门组件源码

1.弹窗组件

  1. CustomDialog
  2. export default struct UserPrivacyDialog {
  3.   controller: CustomDialogController
  4.   confirm: () => void
  5.   cancel: () => void
  6.   build() {
  7.     Column({space: CommonConstants.SPACE_10}){
  8.       // 1.标题
  9.       Text($r('app.string.user_privacy_title'))
  10.         .fontSize(20)
  11.         .fontWeight(CommonConstants.FONT_WEIGHT_700)
  12.       // 2.内容
  13.       Text($r('app.string.user_privacy_content'))
  14.       // 3.按钮
  15.       Button($r('app.string.agree_label'))
  16.         .width(150)
  17.         .backgroundColor($r('app.color.primary_color'))
  18.         .onClick(() => {
  19.           this.confirm()
  20.           this.controller.close()
  21.         })
  22.       Button($r('app.string.refuse_label'))
  23.         .width(150)
  24.         .backgroundColor($r('app.color.lightest_primary_color'))
  25.         .fontColor($r('app.color.light_gray'))
  26.         .onClick(() => {
  27.           this.cancel()
  28.           this.controller.close()
  29.         })
  30.     }
  31.     .width('100%')
  32.     .padding(10)
  33.   }
  34. }
复制代码
2.键盘组件

  1. @Component
  2. export default struct KeyBoard {
  3.   @Link amount:number
  4.   @Link value:string
  5.   @Styles keyBoxStyle(){
  6.     .backgroundColor(Color.White)
  7.     .height(60)
  8.     .borderRadius(8)
  9. }
  10.   numbers:string[]=['1','2','3','4','5','6','7','8','9','0','.']
  11.   build() {
  12.     Grid(){
  13.       ForEach(this.numbers,(num:number)=>{
  14.         GridItem(){
  15.           Text(`${num}`)
  16.             .fontSize(20)
  17.             .fontWeight(CommonConstants.FONT_WEIGHT_900)
  18.         }
  19.         .keyBoxStyle()
  20.         .onClick(()=>this.clickNumber(`${num}`))
  21.       })
  22.       GridItem(){
  23.         Text('删除')
  24.           .fontSize(20)
  25.           .fontWeight(CommonConstants.FONT_WEIGHT_900)
  26.       }
  27.       .keyBoxStyle()
  28.       .onClick(()=>this.clickDelete())
  29.     }
  30.     .width('100%')
  31.     .height(280)
  32.     .backgroundColor($r('app.color.index_page_background'))
  33.     .columnsTemplate('1fr 1fr 1fr')
  34.     .columnsGap(8)
  35.     .rowsGap(8)
  36.     .padding(8)
  37.     .margin({top:10})
  38.   }
  39.   //函数
  40.   clickNumber(num:string){
  41.     //1.拼接用户输入的内容
  42.     let val=this.value+num
  43.     //2.校验输入格式
  44.     let firstIndex=val.indexOf('.')
  45.     let lastIndex=val.lastIndexOf('.')
  46.     if(firstIndex!==lastIndex||(lastIndex!==-1&&lastIndex<val.length-2)){
  47.       //非法输入
  48.       return
  49.     }
  50.     //3.转为数值保存
  51.     let amount=this.parseFloat(val)
  52.     if(amount>99.9){
  53.       this.amount=99.0
  54.       this.value='99'
  55.     }else{
  56.       this.amount=amount
  57.       this.value=val
  58.     }
  59.   }
  60.   clickDelete(){
  61.       if(this.value.length<=0){
  62.         this.value=''
  63.         this.amount=0
  64.         return
  65.       }
  66.       this.value=this.value.substring(0,this.value.length-1)
  67.       this.amount=this.parseFloat(this.value)
  68.   }
  69.   parseFloat(str:string){
  70.   if(!str){
  71.     return 0
  72.   }
  73.   if(str.endsWith('.')){
  74.     str=str.substring(0,str.length-1)
  75.   }
  76.   return parseFloat(str)
  77.   }
  78. }
复制代码
3.日期卡片组件

  1. @Component
  2. export default struct StatsCard {
  3.   @StorageProp('selectedDate') selectedDate: number = DateUtil.beginTimeOfDay(new Date())
  4.   @StorageProp('currentBreakpoint') currentBreakpoint: string = BreakpointConstants.BREAKPOINT_SM
  5.   @Consume @Watch('handleRecordsChange') records: RecordVO[]
  6.   @State info: StatsInfo = new StatsInfo()
  7.   handleRecordsChange(){
  8.     this.info = RecordService.calculateStatsInfo(this.records)
  9.   }
  10.   controller: CustomDialogController = new CustomDialogController({
  11.     builder: DatePickDialog({selectedDate: new Date(this.selectedDate)})
  12.   })
  13.   build() {
  14.     Column(){
  15.       // 1.日期信息
  16.       Row(){
  17.         Text(DateUtil.formatDate(this.selectedDate))
  18.           .fontColor($r('app.color.secondary_color'))
  19.         Image($r('app.media.ic_public_spinner'))
  20.           .width(20)
  21.           .fillColor($r('app.color.secondary_color'))
  22.       }
  23.       .padding(CommonConstants.SPACE_8)
  24.       .onClick(() => this.controller.open())
  25.       // 2.统计信息
  26.       Swiper(){
  27.         // 2.1.热量统计
  28.         CalorieStats({intake: this.info.intake, expend: this.info.expend})
  29.         // 2.2.营养素统计
  30.         NutrientStats({carbon: this.info.carbon, protein: this.info.protein, fat: this.info.fat})
  31.       }
  32.       .width('100%')
  33.       .backgroundColor(Color.White)
  34.       .borderRadius(CommonConstants.DEFAULT_18)
  35.       .indicatorStyle({selectedColor: $r('app.color.primary_color')})
  36.       .displayCount(new BreakpointType({
  37.         sm: 1,
  38.         md: 1,
  39.         lg: 2
  40.       }).getValue(this.currentBreakpoint))
  41.     }
  42.     .width(CommonConstants.THOUSANDTH_940)
  43.     .backgroundColor($r('app.color.stats_title_bgc'))
  44.     .borderRadius(CommonConstants.DEFAULT_18)
  45.   }
  46. }
复制代码
4.首页列表组件

  1. @Extend(Text) function grayText(){
  2.   .fontSize(14)
  3.   .fontColor($r('app.color.light_gray'))
  4. }
  5. @Component
  6. export default struct RecordList {
  7.   @Consume @Watch('handleRecordsChange') records: RecordVO[]
  8.   @State groups: GroupInfo<RecordType, RecordVO>[] = []
  9.   handleRecordsChange(){
  10.     this.groups = RecordService.calculateGroupInfo(this.records)
  11.   }
  12.   build() {
  13.     List({space: CommonConstants.SPACE_10}){
  14.       ForEach(this.groups, (group: GroupInfo<RecordType, RecordVO>) => {
  15.         ListItem(){
  16.           Column({space: CommonConstants.SPACE_8}){
  17.             // 1.分组的标题
  18.             Row({space: CommonConstants.SPACE_4}){
  19.               Image(group.type.icon).width(24)
  20.               Text(group.type.name).fontSize(18).fontWeight(CommonConstants.FONT_WEIGHT_700)
  21.               Text(`建议${group.type.min}~${group.type.max}千卡`).grayText()
  22.               Blank()
  23.               Text(group.calorie.toFixed(0)).fontSize(14).fontColor($r('app.color.primary_color'))
  24.               Text('千卡').grayText()
  25.               Image($r('app.media.ic_public_add_norm_filled'))
  26.                 .width(20)
  27.                 .fillColor($r('app.color.primary_color'))
  28.             }
  29.             .width('100%')
  30.             .onClick(() => {
  31.               router.pushUrl({
  32.                 url: 'pages/ItemIndex',
  33.                 params: {type: group.type}
  34.               })
  35.             })
  36.             // 2.组内记录列表
  37.             List(){
  38.               ForEach(group.items, (item: RecordVO) => {
  39.                 ListItem(){
  40.                   Row({space: CommonConstants.SPACE_6}){
  41.                     Image(item.recordItem.image).width(50)
  42.                     Column({space: CommonConstants.SPACE_4}){
  43.                       Text(item.recordItem.name).fontWeight(CommonConstants.FONT_WEIGHT_500)
  44.                       Text(`${item.amount}${item.recordItem.unit}`).grayText()
  45.                     }
  46.                     Blank()
  47.                     Text(`${item.calorie.toFixed(0)}千卡`).grayText()
  48.                   }
  49.                   .width('100%')
  50.                   .padding(CommonConstants.SPACE_6)
  51.                 }.swipeAction({end: this.deleteButton.bind(this)})
  52.               })
  53.             }
  54.             .width('100%')
  55.           }
  56.           .width('100%')
  57.           .backgroundColor(Color.White)
  58.           .borderRadius(CommonConstants.DEFAULT_18)
  59.           .padding(CommonConstants.SPACE_12)
  60.         }
  61.       })
  62.     }
  63.     .width(CommonConstants.THOUSANDTH_940)
  64.     .height('100%')
  65.     .margin({top: 10})
  66.   }
  67.   @Builder deleteButton(){
  68.     Image($r('app.media.ic_public_delete_filled'))
  69.       .width(20)
  70.       .fillColor(Color.Red)
  71.       .margin(5)
  72.   }
  73. }
复制代码
5.食品列表组件

  1. @Component
  2. export default struct ItemList {
  3.   showPanel: (item: RecordItem) => void
  4.   @Prop isFood: boolean
  5.   build() {
  6.     Tabs() {
  7.       TabContent() {
  8.         this.TabContentBuilder(ItemModel.list(this.isFood))
  9.       }
  10.       .tabBar('全部')
  11.       ForEach(
  12.         ItemModel.listItemGroupByCategory(this.isFood),
  13.         (group: GroupInfo<ItemCategory, RecordItem>) => {
  14.           TabContent() {
  15.             this.TabContentBuilder(group.items)
  16.           }
  17.           .tabBar(group.type.name)
  18.         })
  19.     }
  20.     .width(CommonConstants.THOUSANDTH_940)
  21.     .height('100%')
  22.     .barMode(BarMode.Scrollable)
  23.   }
  24.   @Builder TabContentBuilder(items: RecordItem[]) {
  25.     List({ space: CommonConstants.SPACE_10 }) {
  26.       ForEach(items, (item: RecordItem) => {
  27.         ListItem() {
  28.           Row({ space: CommonConstants.SPACE_6 }) {
  29.             Image(item.image).width(50)
  30.             Column({ space: CommonConstants.SPACE_4 }) {
  31.               Text(item.name).fontWeight(CommonConstants.FONT_WEIGHT_500)
  32.               Text(`${item.calorie}千卡/${item.unit}`).fontSize(14).fontColor($r('app.color.light_gray'))
  33.             }.alignItems(HorizontalAlign.Start)
  34.             Blank()
  35.             Image($r('app.media.ic_public_add_norm_filled'))
  36.               .width(18)
  37.               .fillColor($r('app.color.primary_color'))
  38.           }
  39.           .width('100%')
  40.           .padding(CommonConstants.SPACE_6)
  41.         }
  42.         .onClick(() => this.showPanel(item))
  43.       })
  44.     }
  45.     .width('100%')
  46.     .height('100%')
  47.   }
  48. }
复制代码
6.itemPage

  1. @Extend(Button) function panelButtonStyle(){
  2.   .width(120)
  3.   .type(ButtonType.Normal)
  4.   .borderRadius(6)
  5. }
  6. @Entry
  7. @Component
  8. struct ItemIndex {
  9.   @State amount: number = 1
  10.   @State value: string = ''
  11.   @State showPanel: boolean = false
  12.   @State item: RecordItem = null
  13.   @State type: RecordType = RecordTypes[0]
  14.   @State isFood: boolean = true
  15.   onPanelShow(item: RecordItem) {
  16.     this.amount = 1
  17.     this.value = ''
  18.     this.item = item
  19.     this.showPanel = true
  20.   }
  21.   onPageShow(){
  22.     // 1.获取跳转时的参数
  23.     let params: any = router.getParams()
  24.     // 2.获取点击的饮食记录类型
  25.     this.type = params.type
  26.     this.isFood = this.type.id !== RecordTypeEnum.WORKOUT
  27.   }
  28.   build() {
  29.     Column() {
  30.       // 1.头部导航
  31.       this.Header()
  32.       // 2.列表
  33.       ItemList({ showPanel: this.onPanelShow.bind(this), isFood: this.isFood })
  34.         .layoutWeight(1)
  35.       // 3.底部面板
  36.       Panel(this.showPanel) {
  37.         // 3.1.顶部日期
  38.         ItemPanelHeader()
  39.         // 3.2.记录项卡片
  40.         if(this.item){
  41.           ItemCard({amount: this.amount, item: $item})
  42.         }
  43.         // 3.3.数字键盘
  44.         NumberKeyboard({amount: $amount, value: $value})
  45.         // 3.4.按钮
  46.         this.PanelButton()
  47.       }
  48.       .mode(PanelMode.Full)
  49.       .dragBar(false)
  50.       .backgroundMask($r('app.color.light_gray'))
  51.       .backgroundColor(Color.White)
  52.     }
  53.     .width('100%')
  54.     .height('100%')
  55.   }
  56.   @Builder PanelButton(){
  57.     Row({space: CommonConstants.SPACE_6}){
  58.       Button('取消')
  59.         .panelButtonStyle()
  60.         .backgroundColor($r('app.color.light_gray'))
  61.         .onClick(() => this.showPanel = false)
  62.       Button('提交')
  63.         .panelButtonStyle()
  64.         .backgroundColor($r('app.color.primary_color'))
  65.         .onClick(() => {
  66.           // 1.持久化保存
  67.           RecordService.insert(this.type.id, this.item.id, this.amount)
  68.             .then(() => {
  69.               // 2.关闭弹窗
  70.               this.showPanel = false
  71.             })
  72.         })
  73.     }
  74.     .margin({top: 10})
  75.   }
  76.   @Builder Header() {
  77.     Row() {
  78.       Image($r('app.media.ic_public_back'))
  79.         .width(24)
  80.         .onClick(() => router.back())
  81.       Blank()
  82.       Text(this.type.name).fontSize(18).fontWeight(CommonConstants.FONT_WEIGHT_600)
  83.     }
  84.     .width(CommonConstants.THOUSANDTH_940)
  85.     .height(32)
  86.   }
  87. }
复制代码
感爱好的小同伴可以把去堆栈把源码下载下来运行看结果。由于篇幅过长,现在只展示部门源码。键盘组件、列表组件终极结果图如图3、4所示,弹窗组件结果见图1,首页结果见图2。

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

×
回复

使用道具 举报

登录后关闭弹窗

登录参与点评抽奖  加入IT实名职场社区
去登录
快速回复 返回顶部 返回列表