#我的鸿蒙开发手记#鸿蒙婚恋首页模块练习 原创

惠广优品DXB
发布于 2025-5-6 10:33
浏览
0收藏

一、背景与说明
1.背景
本模块是基于 HarmonyOS 5.0.0 及以上版本、DevEco Studio 5.0.0 及以上版本等环境进行鸿蒙婚恋首页模块的练习。介绍了婚恋首页整体构建布局设计,包括首页模块(Home.ets)以及主界面结构模块代码实现(Index.ets)。在 Index.ets 中,通过导入相关模块,构建了包含首页、动态列表、会员、个人中心等标签页的导航容器,并实现了静默登录、获取用户信息等业务逻辑。阐述了工程结构,首页功能位于 SocialDating 工程的 composite 模块中,包含页面文件、数据模型、服务层等。重点对首页界面结构(Home.ets)进行了介绍,包括模块导入、常量定义、状态管理、生命周期方法以及页面构建等方面的内容。说明了如何通过构建顶部导航栏、页签容器、推荐用户模块、活动列表模块等来实现婚恋首页的功能,其中涉及获取当前用户信息、加载推荐用户、查询红娘信息等操作。同时,还介绍了范围展示组件、枚举值展示组件、单数据展示组件等辅助组件的开发。
2.说明
(1)设备类型:手机
(2)HarmonyOS版本:HarmonyOS 5.0.0 Release及以上
(3)DevEco Studio版本:DevEco Studio 5.0.0 Release及以上
(4)HarmonyOS SDK版本:HarmonyOS 5.0.0 Release SDK及以上
二、婚恋首页整体构建布局设计
1.模块设计
首页模块:Home.ets
(1)主界面结构模块代码实现(Index.ets):

// 导入依赖模块
import { TabItem, tabItems } from '../model/TabItem';            // 标签页模型定义
import { authentication } from '@kit.AccountKit';                // 华为账号认证服务
import { BusinessError } from '@kit.BasicServicesKit';           // 基础服务错误类型
import { BasicUserInfo, GetUserResponse, UserInfo, Constants, MockService } from 'commons'; // 公共模块定义
import { Home, Personal } from 'composite';                     // 首页和个人中心组件
import { MemberPage } from 'member';                            // 会员页面组件
import { FeedListPage } from 'feed';                            // 动态列表页面组件
import UserService from 'user/src/main/ets/service/UserService';// 用户服务模块
import Logger from 'commons/src/main/ets/utils/Logger';          // 日志工具

@Entry                            
@Component                        
struct Index {                    
  @State currentPageIndex: number = 0;           // 当前选中页签的索引(响应式状态)
  @State currentUser: BasicUserInfo | null = null; // 当前用户基本信息(响应式状态)
  @Provide('pageStack') pageStack: NavPathStack = new NavPathStack(); // 提供导航堆栈实例

  build() {                        // 组件UI构建方法
    Navigation(this.pageStack) {   // 导航容器包裹整个页面
      this.buildPageTitle()        // 自定义页面标题区域
      Tabs({ 
        barPosition: BarPosition.End,  // 标签栏位置:底部
        index: this.currentPageIndex    // 绑定当前选中页签索引
      }) {
        // ----------- 首页标签页 -----------
        TabContent() {
          Home({ index: this.currentPageIndex }) // 首页组件
        }.tabBar(this.buildTabBar(               // 自定义标签栏样式
          tabItems[Constants.PAGE_INDEX_HOME],   // 获取首页标签配置
          Constants.PAGE_INDEX_HOME              // 标签索引
        ))
        .expandSafeArea([SafeAreaType.SYSTEM], [SafeAreaEdge.BOTTOM]) // 安全区域适配

        // ----------- 动态列表标签页 -----------
        TabContent() {
          //FeedListPage({ index: this.currentPageIndex }) // 动态页面组件
        }.tabBar(this.buildTabBar(
          tabItems[Constants.PAGE_INDEX_FEED], 
          Constants.PAGE_INDEX_FEED
        ))
        .expandSafeArea([SafeAreaType.SYSTEM], [SafeAreaEdge.BOTTOM])

        // ----------- 会员标签页 -----------
        TabContent() {
          //MemberPage({ index: this.currentPageIndex })   // 会员页面组件
        }.tabBar(this.buildTabBar(
          tabItems[Constants.PAGE_INDEX_MEMBER], 
          Constants.PAGE_INDEX_MEMBER
        ))
        .expandSafeArea([SafeAreaType.SYSTEM], [SafeAreaEdge.BOTTOM])

        // ----------- 个人中心标签页 -----------
        TabContent() {
          //Personal({ index: this.currentPageIndex })     // 个人中心组件
        }.tabBar(this.buildTabBar(
          tabItems[Constants.PAGE_INDEX_PERSONAL], 
          Constants.PAGE_INDEX_PERSONAL
        ))
        .expandSafeArea([SafeAreaType.SYSTEM], [SafeAreaEdge.BOTTOM])
      }
      .width('100%')              // 标签栏宽度占满
      .barHeight(110)             // 标签栏高度
      .onChange((index: number) => {  // 页签切换回调
        this.currentPageIndex = index; // 更新当前页签索引
      })
      .vertical(false)            // 水平排列标签
      .backgroundColor($r('app.color.page_background_color')) // 背景色
    }
    .hideTitleBar(true)           // 隐藏系统标题栏
    .hideToolBar(true)            // 隐藏系统工具栏
  }

  // ------------------- 自定义构建方法 -------------------
  @Builder
  buildPageTitle() {              // 构建页面标题区域
    Row() {
      // 根据当前页签显示对应标题
      if (this.currentPageIndex === Constants.PAGE_INDEX_HOME) {
        this.buildPageTitleText(tabItems[Constants.PAGE_INDEX_HOME].title)
      } else if (...) { /* 其他页签条件判断 */ }
    }
    .width('92%').height(56)      // 标题栏尺寸
  }

  @Builder
  buildPageTitleText(pageTitle: string) {  // 构建标题文本样式
    Text(pageTitle)
      .fontColor($r('app.color.text_font_color_black')) 
      .fontSize(24)                     
      .fontWeight(FontWeight.Medium)    
  }

  @Builder
  buildTabBar(button: TabItem, tabIndex: number) { // 构建单个标签项
    Column() {
      Image(this.currentPageIndex === tabIndex ? button.selectedImage : button.img) // 动态切换图标
        .objectFit(ImageFit.Contain)      // 图片填充方式
        .width(24).height(24)
      Text(button.title)                  // 标签文字
        .fontColor(this.currentPageIndex === tabIndex ? 
          button.selectedTitleColor :     // 选中状态颜色
          $r('app.color.text_font_color_gray') 
        )
    }
    .onClick(() => {                      // 点击切换页签
      this.currentPageIndex = tabIndex; 
    })
  }

  // ------------------- 生命周期方法 -------------------
  aboutToAppear(): void {          // 组件即将显示时触发
    MockService.init();            // 初始化模拟数据服务
    this.silentLogin();            // 执行静默登录
  }

  // ------------------- 业务逻辑方法 -------------------
  private silentLogin() {           // 静默登录逻辑
    let loginRequest = new authentication.HuaweiIDProvider().createLoginWithHuaweiIDRequest();
    loginRequest.forceLogin = false; // 非强制登录(无感知登录)
    let controller = new authentication.AuthenticationController();
    controller.executeRequest(loginRequest).then((data) => {
      let unionId = data.unionID;   // 获取用户唯一标识
      Logger.info('静默登录成功, unionId=' + unionId);
      AppStorage.setOrCreate(Constants.UNION_ID, unionId); // 存储到应用全局状态
      this.getUserInfo(unionId);    // 获取用户详细信息
    }).catch((error: BusinessError) => {
      if (error.code === authentication.AuthenticationErrorCode.ACCOUNT_NOT_LOGGED_IN) {
        loginRequest.forceLogin = true; // 触发强制登录(显示登录界面)
        controller.executeRequest(loginRequest).then((data) => { /* 处理强制登录 */ })
      }
    })
  }

  getUserInfo(unionId: string | undefined) {  // 获取用户信息
    UserService.getUserFullInfoByUnionId(unionId).then((data: GetUserResponse | null) => {
      if (data?.userFullInfo?.userInfo) {     // 已注册用户
        this.currentUser = UserInfo.toBasicUserInfo(data.userFullInfo.userInfo);
        AppStorage.setOrCreate(Constants.CURRENT_USER_BASIC, this.currentUser); // 更新全局用户信息
      } else {                                // 未注册用户
        this.pageStack.pushPathByName('UserRegister', null, (popInfo: PopInfo) => {
          this.currentUser = popInfo.result as BasicUserInfo; // 注册完成后更新用户信息
        });
      }
    })
  }
}

2.工程结构
首页功能位于SocialDating工程的composite模块中,主要包含以下文件:
(1)页面文件:RecommendPage.ets(推荐页)、ActivityPage.ets(活动页)
(2)数据模型:User.ets(用户信息模型)、Activity.ets(活动信息模型)
(3)服务层:MockService.ets(本地模拟数据服务)
(4)首页界面结构(Home.ets):

// ------------------- 模块导入 -------------------
import { ActivityInfo, QueryActivityResponse } from 'activity';         // 活动模块:活动信息模型与查询响应类型
import {
  GetUserResponse, UserFullInfo, UserInfo, BasicUserInfo, Constants, 
  DateUtils, QueryFeedType, UiUtils, FollowType
} from 'commons'; // 公共模块:用户数据模型、工具类、常量定义
import { Matchmaker, contactMatchmakerBuilder, bindSheetTitleBuilder } from 'member'; // 会员模块:红娘功能相关
import { FeedListPageRouteParam } from 'feed';      // 动态模块:动态列表路由参数
import { UserSelfIntro, UserLabels } from 'user';   // 用户模块:自我介绍和标签组件
import { GridShowImage } from 'feed';               // 动态模块:图片网格展示组件

// 服务类导入
import MatchMakerService from 'member/src/main/ets/service/MatchMakerService'; // 红娘服务
import FeedService from 'feed/src/main/ets/service/FeedService';               // 动态服务
import Logger from 'commons/src/main/ets/utils/Logger';                        // 日志工具
import UserService from 'user/src/main/ets/service/UserService';               // 用户服务
import ActivityService from 'activity/src/main/ets/service/ActivityService';   // 活动服务

// ------------------- 常量定义 -------------------
const HOME_OPER_DRAG_MIN_RIGHT = 20;    // 操作按钮拖拽区域最小右边界
const HOME_OPER_DRAG_MAX_RIGHT = 320;   // 操作按钮拖拽区域最大右边界
const HOME_OPER_DRAG_MIN_BOTTOM = 60;   // 操作按钮拖拽区域最小下边界
const HOME_OPER_DRAG_MAX_BOTTOM = 500;  // 操作按钮拖拽区域最大下边界
const MIN_UP_OFFSET = -300;             // 上滑触发加载的偏移阈值

// ------------------- 首页组件定义 -------------------
@Component
export struct Home {
  // ----------- 状态管理 -----------
  @State currentIndex: number = 0;              // 当前页签索引(0-推荐,1-活动)
  @Consume('pageStack') pageStack: NavPathStack; // 导航堆栈(跨组件共享)
  @State selectedCity: string = '南京';         // 当前选中的活动城市
  private selectCityIdx: number = 0;            // 城市选择器选中项索引
  private activitiesScroller: Scroller = new Scroller(); // 活动列表滚动控制器
  @State arr: Array<ActivityInfo> = [];         // 活动数据列表
  @State selectedActivity: ActivityInfo | null = null; // 当前选中的活动
  @State recommendUserFullInfo: UserFullInfo | null = null; // 推荐用户的完整信息
  @State followClicked: boolean = false;        // 关注按钮点击状态
  private userInfoScroller: Scroller = new Scroller(); // 用户信息滚动控制器
  private recommendNumber = 1;                  // 当前推荐用户的序号
  private matchMaker: Matchmaker | null = null; // 红娘信息
  @State ta: string = 'TA';                     // 根据性别动态显示“他”或“她”
  @State currentUser: BasicUserInfo | null = null; // 当前登录用户基本信息
  @State isShowContactMatchmaker: boolean = false; // 是否显示联系红娘弹窗
  @State operButtonsRight: number = 20;         // 操作按钮右侧位置(拖拽用)
  @State operButtonsBottom: number = 50;        // 操作按钮底部位置(拖拽用)
  @Prop @Watch('onChange') index: number = 0;   // 监听外部传入的页签索引变化

  // ----------- 生命周期方法 -----------
  aboutToAppear(): void {
    this.getCurrentUser();          // 获取当前用户信息
    if (this.currentUser != null) {
      this.getRecommendUser(this.recommendNumber); // 加载推荐用户
    }
    MatchMakerService.queryMatchMaker().then((data: Matchmaker[]) => {
      this.matchMaker = data[0];    // 获取红娘信息
    })
  }

  // ----------- 核心方法 -----------
  // 获取当前用户信息(优先从全局状态读取)
  private getCurrentUser() {
    let userBasicTmp: BasicUserInfo | null | undefined = AppStorage.get(Constants.CURRENT_USER_BASIC);
    if (userBasicTmp) {
      this.currentUser = userBasicTmp; // 从全局状态读取
    } else {
      // 未存储时通过服务查询
      UserService.getUserFullInfoByUnionId(AppStorage.get(Constants.UNION_ID)).then((data: GetUserResponse | null) => {
        if (data?.userFullInfo?.userInfo) {
          this.currentUser = UserInfo.toBasicUserInfo(data.userFullInfo.userInfo);
        }
      })
    }
  }

  // ----------- 页面构建 -----------
  build() {
    NavDestination() {
      this.buildTabBarRow()  // 自定义顶部导航栏

      // 页签容器(推荐 & 活动)
      Tabs({ barPosition: BarPosition.Start, index: this.currentIndex }) {
        // 推荐页内容
        TabContent() { this.recommendUser(); }
        // 活动页内容
        TabContent() { this.activityList(); }
      }
      .barHeight(0) // 隐藏默认标签栏
      .onChange((index: number) => {
        this.currentIndex = index;
        if (index === 1) this.getActivities(); // 切换至活动页时加载数据
      })
    }
    .hideTitleBar(true)
    .backgroundColor($r('app.color.page_background_color'))
  }

  // ----------- 自定义构建方法 -----------
  // 构建顶部导航栏(包含筛选图标和页签切换)
  @Builder
  buildTabBarRow() {
    Row() {
      // 推荐页签区域
      Row() {
        // 筛选图标(仅推荐页显示)
        if (this.currentIndex === 0) {
          Image($r('app.media.adjustment'))
            .onClick(() => this.pageStack.pushPathByName('UserSearchPage', null)) // 跳转搜索页
        } else {
          Blank().width(24).height(24) // 占位空白
        }
        // 推荐页签文本
        Text('推荐')
          .fontColor(this.currentIndex === 0 ? $r('app.color.button_background_color') : Color.Black)
          .onClick(() => this.currentIndex = 0)
          .margin({ left: 80 })
      }.margin({ right: 24 })

      // 活动页签区域
      Row() {
        Text('活动')
          .fontColor(this.currentIndex === 1 ? $r('app.color.button_background_color') : Color.Black)
          .onClick(() => this.currentIndex = 1)
      }.margin({ left: 24, right: 120 })
    }
    .backgroundColor($r('app.color.page_background_color'))
    .margin({ bottom: 16 })
  }

  // ----------- 活动列表模块 -----------
  @Builder
  activityList() {
    Column() {
      this.selectCity()     // 城市选择器
      this.scrollActivities() // 活动滚动列表
    }.height('100%')
  }

  // 城市选择器
  @Builder
  selectCity() {
    Stack() {
      Image($r('app.media.icon_location')) // 定位图标
      Select(this.buildCitySelectOptions()) // 动态生成城市选项
        .selected(this.selectCityIdx)
        .value('活动地区: ' + this.selectedCity)
        .onSelect((index: number, text?: string) => {
          this.selectCityIdx = index;
          if (text) {
            this.selectedCity = text;
            this.getActivities(); // 城市变更时重新加载活动
          }
        })
    }.width('92%')
  }

  // ----------- 推荐用户模块 -----------
  @Builder
  recommendUser() {
    Stack() {
      // 用户信息滚动区域
      Column() { this.showUser() }

      // 操作按钮组(可拖拽)
      Column() {
        // 联系红娘按钮
        Image($r('app.media.icon_matchmaker'))
          .onClick(() => {
            if (this.currentUser?.isVip === Constants.YES) {
              this.isShowContactMatchmaker = !this.isShowContactMatchmaker; // 显示弹窗
            } else {
              this.pageStack.pushPathByName('Member', null); // 引导开通会员
            }
          })
          .bindSheet($$this.isShowContactMatchmaker, contactMatchmakerBuilder(...)) // 绑定底部弹窗

        // 关注/取消关注按钮
        Image(this.followClicked ? $r('app.media.icon_follow') : $r('app.media.icon_followed'))
          .onClick(() => {
            this.followClicked = !this.followClicked;
            if (this.recommendUserFullInfo?.userInfo?.uid) {
              FeedService.followUser(...); // 调用关注接口
            }
          })

        // 跳过按钮
        Image($r('app.media.icon_cross'))
          .onClick(() => {
            if (this.recommendNumber >= this.getMaxRecommend()) {
              UiUtils.showAlertDialog('已达推荐上限'); // 提示开通会员
            } else {
              this.recommendNumber += 1;
              this.getRecommendUser(this.recommendNumber); // 加载下一个用户
            }
          })
      }
      .position({ right: this.operButtonsRight, bottom: this.operButtonsBottom })
      .gesture(PanGesture()... ) // 拖拽手势处理
    }
  }

  // ----------- 工具方法 -----------
  // 获取最大推荐用户数(普通用户/VIP不同)
  getMaxRecommend(): number {
    return this.currentUser?.isVip === Constants.YES ? 
      Constants.MAX_RECOMMEND_USER_VIP : 
      Constants.MAX_RECOMMEND_USER_NORMAL;
  }
}

// ------------------- 辅助组件 -------------------
// 范围展示组件(如年龄22-30岁)
@Component
export struct RangeRow {
  @Prop start: number | null;  // 起始值
  @Prop end: number | null;     // 结束值
  @Prop prefix: string;         // 前缀标签(如“年龄”)

  build() {
    if (this.start || this.end) {
      Row() {
        Text(`#${this.prefix}`) // 标签
        // 动态显示范围值
        if (this.start && this.end) {
          Text(`${this.start} - ${this.end}`)
        } else if (this.start) {
          Text(`${this.start}+`)
        } else if (this.end) {
          Text(`${this.end}-`)
        }
      }
    }
  }
}

// 枚举值展示组件(如婚姻状况:未婚)
@Component
export struct EnumRow {
  @Prop dataValue: string | null;     // 原始数据(如"0")
  @Prop displayValues: string[];      // 显示文本映射(["未婚", "已婚"])
  @Prop itemName: string;             // 条目名称(如“婚姻状况”)

  build() {
    if (this.dataValue) {
      Row() {
        Text(`#${this.itemName}`) // 标签
        // 分割多选值并映射显示
        ForEach(this.dataValue.split(','), (item: string) => {
          Text(this.displayValues[parseInt(item)])
        })
      }
    }
  }
}

// 单数据展示组件(如现居地:北京)
@Component
export struct DataRow {
  @Prop dataValue: string | null; // 数据值
  @Prop itemName: string;         // 条目名称

  build() {
    if (this.dataValue) {
      Row() {
        Text(`#${this.itemName}`) // 标签
        Text(this.dataValue)       // 值
      }
    }
  }
}

三、最终效果
#我的鸿蒙开发手记#鸿蒙婚恋首页模块练习-鸿蒙开发者社区

©著作权归作者所有,如需转载,请注明出处,否则将追究法律责任
分类
收藏
回复
举报
回复
    相关推荐