
#我的鸿蒙开发手记#鸿蒙婚恋首页模块练习 原创
一、背景与说明
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) // 值
}
}
}
}
三、最终效果
