【江鸟中原】鸿蒙仿微信APP
该摘要介绍了基于HarmonyOS ArkUI框架开发的微信仿制应用项目,实现了从登录到主界面的完整流程,包含聊天、通讯录等核心功能。项目采用组件化架构,代码注释详细,适合初学者学习。文档涵盖项目结构、启动流程、功能实现、开发环境配置和学习路径,帮助开发者掌握HarmonyOS开发基础和UI设计原则。完整源码已开源,可作为实践参考。
一、文档说明
本文介绍了一个基于HarmonyOS ArkUI框架开发的微信仿制应用项目。该项目实现了从登录到主界面的完整流程,包含聊天、通讯录、发现和个人中心等核心功能模块。项目特色包括组件化架构、详细代码注释和初学者友好设计。
二、项目概述
文档详细讲解了项目结构、启动流程和各功能模块的实现,如预登录页面、登录验证、主界面Tabs布局、聊天消息发送与接收等。同时提供了开发环境配置指南、学习路径建议和功能状态说明。该项目适合编程初学者学习移动应用开发,掌握HarmonyOS开发基础,理解组件化思想和UI设计原则。完整源码已开源,可作为HarmonyOS应用开发的实践参考。
三、项目特色
完整功能流程:从登录到各个功能模块的完整体验。
组件化架构:代码结构清晰,便于维护和扩展。
详细注释:每个关键代码都有详细说明。
小白友好:用最简单的语言解释复杂概念。
3.1项目结构详解
3.1.1pages页面文件——应用的不同界面

3.1.2components组件文件——可复用的UI模块

3.1.3viewmodels数据模型——数据结构定义

3.2应用启动流程
3.2.1第一步:预登录页面 (PreLogin.ets)
预登录页面是用户打开应用后看到的第一个界面,作为应用入口提供登录和注册两个主要功能选项。该页面采用简洁的UI设计,通过背景图片和按钮布局营造微信应用的熟悉感。

其中的布局设计有:
- Stack布局:采用堆叠布局实现背景图片和按钮的层次关系。
- ImageFit.Cover:确保背景图片全屏覆盖,保持比例。
- 按钮布局:使用Row和Column组合实现按钮的横向排列和垂直居中。
注意:默认程序运行后的第一个界面为Index页面,因此,若想打开后的第一个界面是PreLogin需要在/entry/src/main/ets/entryability下的EntryAbility文件中修开首页面的位置,即将windowStage.loadContent('pages/Index, (err) => {}中的Index改为PreLogin即可。
预登录界面展示

3.2.2第二步:登录页面 (Login.ets)
import { promptAction } from "@kit.ArkUI";
@Entry
@Component
export struct Login{
@State params:object=this.getUIContext().getRouter().getParams()
@State isLoading:boolean=false;
@State username:string="";
@State password:string="";
@State showError:boolean=false;
build() {
Column(){
Text("微信")
.fontColor(Color.Gray)
.fontSize(20)
.margin({left:-300})
.align(Alignment.Start)
Image($r("app.media.Yao"))//引用media文件夹中的图片资源
.width(160)
.height(160)
.borderRadius(10)
.margin({top:100})
if(this.isLoading){
Text("正在进入……")
.fontColor(Color.Green)
.fontSize(20)
.margin({top:30})
Text("取消")
.fontColor("#0047AB")
.margin({top:400})
.onClick(()=>{
this.isLoading=false
})
}else{
TextInput({placeholder:"用户名"})
.width("80%")
.height(50)
.margin(10)
.onChange((value:string)=>{
this.username=value;
})
TextInput({placeholder:"密码"})
.width("80%")
.height(50)
.margin(10)
.type(InputType.Password)
.onChange((value:string)=>{
this.password=value;
})
if(this.showError){
Text("用户名或密码错误!")
.fontSize(20)
.fontColor(Color.Red)
.margin({top:30})
Text("请重新输入!!!")
.fontSize(20)
.fontColor(Color.Red)
.margin({top:15})
}
Button("进入微信")
.width("60%")
.height(50)
.backgroundColor("#07C160")
.borderRadius(10)
.fontColor(Color.White)
.margin({top:30})
.onClick(()=>{
this.showError=false;
if(this.username=="安於"&&this.password=="123456"){
this.isLoading=true;
setTimeout(()=>{
this.isLoading=false;
//页面跳转
this.getUIContext().getRouter().pushUrl({
url:'pages/Index'
})
},2000);
}else{
this.showError=true;
}
})
Row(){
// 扫一扫按钮
Button('切换账号')
.width('45%')
.height(40)
.fontSize(16)
.fontColor("#0047AB")
.backgroundColor(Color.White)
.borderRadius(20)
.margin({ top: 20 })
.onClick(() => {
promptAction.showToast({
message:"未实际该功能!!",
duration:2000
})
})
Button('仅传输文件')
.width('45%')
.height(40)
.fontSize(16)
.fontColor("#0047AB")
.backgroundColor(Color.White)
.borderRadius(20)
.margin({ top: 20 })
.onClick(() => {
promptAction.showToast({
message:"未实际该功能!!",
duration:2000
})
})
}
/*Row(){
Text("切换账号")
.fontColor("#0047AB")
.margin({top:20,right:50})
Text("仅传输文件")
.fontColor("#0047AB")
.margin({top:20})
}
.margin({top:10})*/
}
}
.width('100%')
.height('100%')
}
}
登录功能
- 提供用户名和密码输入框,密码输入框采用遮罩显示。
- 实现表单验证逻辑,当用户名输入"安於"且密码为"123456"时视为登录成功。
- 登录成功后显示"正在进入……"加载提示,2秒后自动跳转到主页面(pages/Index)。
- 登录失败时显示红色错误提示信息。
交互体验
- 登录过程中可通过"取消"按钮中断操作。
- 提供"切换账号"和"仅传输文件"两个辅助功能按钮,当前仅显示提示信息。
- 所有按钮和交互元素均采用微信风格设计,包括绿色登录按钮和蓝色文字按钮。
其中身份验证和登录逻辑验证的实现是:
- 进行用户身份验证。

- 下述图片代码为登录验证逻辑。

注意:测试账号——用户名"安於",密码"123456"。
登录界面展示

3.2.3第三步:主页面 (Index.ets)
该页面是应用的核心主界面,采用底部标签页(Tabs)布局,构建了完整的微信功能框架。
import { FinderComponent } from '../components/FinderComponent'
import { MeComponent } from '../components/MeComponent'
import { NoteBookComponent } from '../components/NoteBookComponent'
import { WeChatComponent } from '../components/WeChatComponent'
@Entry
@Component
struct Index {
@State curIndex:number=0
@Builder buildTabBar(title:string,normalImg:Resource,selectedImg:Resource,index:number){
Column(){
Image(this.curIndex==index?selectedImg:normalImg)
.width(32)
.height(32)
.margin({top:8})
Text(title)
.fontSize(16)
.fontColor(this.curIndex==index?Color.Green:Color.Black)
.fontWeight(500)
.margin({
top:2
})
}.height(60)
}
build() {
Tabs(){
TabContent(){
WeChatComponent()
}
.tabBar(this.buildTabBar('微信',$r('app.media.wechat'),$r('app.media.wechat_selected'),0))
TabContent(){
NoteBookComponent()
}
.tabBar(this.buildTabBar('通信录',$r('app.media.notebook'),$r('app.media.notebook_selected'),1))
TabContent(){
FinderComponent()
}
.tabBar(this.buildTabBar('发现',$r('app.media.finder'),$r('app.media.finder_selected'),2))
TabContent(){
MeComponent()
}
.tabBar(this.buildTabBar('我',$r('app.media.user'),$r('app.media.user_selected'),3))
}
.width('100%')
.barPosition(BarPosition.End)
.backgroundColor('#f8f7f6')
.onChange((index:number)=>{
this.curIndex=index
})
}
}
整体架构
- 使用Tabs组件作为容器,底部固定(BarPosition.End)的标签栏设计。
- 页面背景色设置为浅灰色(#f8f7f6),符合微信的视觉风格。
- 通过@State curIndex状态变量精确控制当前选中的标签页索引。
页面集成了四个核心功能模块,每个模块均封装为独立的自定义组件:
- 微信(WeChatComponent):聊天消息主界面。
- 通信录(NoteBookComponent):联系人管理界面。
- 发现(FinderComponent):朋友圈及扩展功能入口。
- 我(MeComponent):个人中心与设置页面。
自定义标签栏设计
- 采用@Builder装饰器创建了可复用的buildTabBar方法,统一管理标签栏样式。
- 实现了图标和文字的动态切换效果:选中状态:图标高亮,文字显示为绿色;未选中状态:图标默认,文字显示为黑色。
交互逻辑
- 通过onChange事件监听标签页切换,实时更新curIndex状态。
- 状态变化驱动UI更新,确保图标和文字颜色的同步切换。
- 组件化设计使得各功能模块高度解耦,便于独立开发和维护。
主页面界面展示

3.3核心功能模块详解
3.3.1. 微信模块 (WeChatComponent + ChatPage)
- 聊天列表页面 (WeChatComponent.ets)
该页面实现了微信的核心聊天列表界面,复刻了微信消息会话的主要功能与交互体验。
import { MessageModel } from "../viewmodels/MessageModel"
import { NavTileBar } from "./NavTitleBar"
import { router } from "@kit.ArkUI"
@Component
export struct WeChatComponent{
@State messages: MessageModel[] = [
{
avatar:$r('app.media.GuiGui'),
nickName:'a闺蜜٩(*´◐`*)۶',
message:'但是我们有课社呀!!',
time:'10:58'
},
{
avatar:$r('app.media.Yao'),
nickName:'安於(*^ω^*)',
message:'[聊天记录]',
time:'11:33'
},
{
avatar: $r('app.media.Mother'),
nickName: '妈妈',
message: '[视频时长 56:16]',
time: '12:36'
},
{
avatar: $r('app.media.Father'),
nickName: '爸爸',
message: '今天这边出太阳了',
time: '15:20'
},
{
avatar: $r('app.media.GrandMa'),
nickName: '奶奶',
message: '[视频时长 46:36]',
time: '19:30'
},
{
avatar: $r('app.media.RoXi'),
nickName: '荣茜',
message: '天气这么好,周末出门去公园吧!!!!',
time: '昨天 18:30'
},
{
avatar: $r('app.media.Qian'),
nickName: '颜纤儿',
message: '好滴好滴!!',
time: '昨天 15:25'
},
{
avatar: $r('app.media.A_ran'),
nickName: '阿冉',
message: 'ok ok!!',
time: '昨天 12:11'
},
{
avatar: $r('app.media.Cao'),
nickName: '校草',
message: '[视频号] 山东绳结达人的视频',
time: '昨天 08:47'
},
{
avatar: $r('app.media.Han'),
nickName: '怡晗',
message: '收到!!',
time: '11月8日'
},
{
avatar: $r('app.media.Yu'),
nickName: '小鱼',
message: '哈哈哈哈哈!!',
time: '11月7日'
},
{
avatar: $r('app.media.Qi'),
nickName: '伊琦',
message: '[视频时长 01:36]',
time: '11月6日'
}
]
@Builder buildMessage(message: MessageModel, index: number) {
ListItem() {
Row() {
// 聊天内容
Row() {
Image(message.avatar)
.width(40)
.height(40)
.borderRadius(5)
.margin({ left: 10 })
Column() {
Text(message.nickName)
.fontSize(14)
.fontWeight(500)
Text(message.message)
.fontSize(12)
.fontColor(Color.Grey)
}
.layoutWeight(1)
.alignItems(HorizontalAlign.Start)
.margin({ left: 10 })
Column() {
Text(message.time)
.fontColor(Color.Black)
.fontSize(12)
.opacity(0.4)
.margin({ right: 10 })
}
.width(80)
.alignItems(HorizontalAlign.End)
}
.width('100%')
.justifyContent(FlexAlign.Start)
.onClick(() => {
// 点击进入聊天页面
this.enterChatPage(message)
})
// 删除按钮
Row() {
Button('删除')
.width(80)
.height(60)
.backgroundColor(Color.Red)
.fontColor(Color.White)
.onClick(() => {
this.deleteMessage(index)
})
}
.width(80)
.justifyContent(FlexAlign.Center)
}
.width('100%')
.height(60)
}
.swipeAction({ end: this.buildDeleteButton(index) })
}
@Builder buildDeleteButton(index: number) {
Button('删除')
.width(80)
.height(60)
.backgroundColor(Color.Red)
.fontColor(Color.White)
.onClick(() => {
this.deleteMessage(index)
})
}
// 进入聊天页面
private enterChatPage(message: MessageModel) {
try {
router.pushUrl({
url: 'pages/ChatWrapperPage',
params: {
avatar: message.avatar,
nickName: message.nickName,
messages: [],// 可以传入历史消息
}
})
console.log('路由跳转成功')
} catch (error) {
console.error('路由跳转失败:', error)
}
}
// 删除消息
private deleteMessage(index: number) {
this.messages.splice(index, 1)
}
build() {
Column() {
NavTileBar({ title: '微信'})
List() {
ForEach(this.messages, (message: MessageModel, index: number) => {
this.buildMessage(message, index)
})
}
.width('100%')
.layoutWeight(1)
.divider({
strokeWidth: 2,
startMargin: 60
})
}
.width('100%')
.height('100%')
.backgroundColor(Color.White)
}
}
界面布局
- 顶部采用自定义的NavTileBar组件,显示"微信"标题。
- 主体部分使用List组件展示消息列表,每条消息间通过细分割线区分。
消息数据模型
- 通过MessageModel结构化定义消息数据,包含头像、昵称、消息内容和时间等关键字段。
- 预置了12条模拟数据,涵盖好友聊天、家人对话、视频消息、视频号分享等多种消息类型。
高级交互功能
- 实现了左滑删除操作,用户可通过swipeAction向左滑动列表项显示红色删除按钮。
- 删除操作通过splice方法直接从messages数组中移除对应项,实现UI的实时更新。
其中主要的模块有:
- 在该页面中显示最近的聊天记录(预制的)。

- 点击任意列表项可通过router.pushUrl方法跳转到对应的聊天详情页(ChatWrapperPage),并传递联系人信息

- 通过enterChatPage方法进入到ChatWapperPage页面中使用ChatPage接口。

删除界面展示

- 聊天页面 (ChatPage.ets)
该页面实现了一个完整的即时聊天界面,为用户提供了与联系人进行实时消息交互的功能。
import { promptAction, router } from '@kit.ArkUI'
// 导出接口供其他文件使用
export interface ChatMessage {
id: string
content: string
time: string
isSelf: boolean
}
export interface ChatParams {
avatar: Resource
nickName: string
messages?: ChatMessage[]
}
@Component
export struct ChatPage {
@Prop params: ChatParams = {} as ChatParams
@State avatar: Resource = $r('app.media.Yao')
@State nickName: string = ''
@State inputText: string = ''
@State messages: ChatMessage[] = [
{
id: '1',
content: '你好!',
time: '10:30',
isSelf: false
},
{
id: '2',
content: '你好,最近怎么样?',
time: '10:31',
isSelf: true
},
{
id: '3',
content: '还不错,你呢?',
time: '10:32',
isSelf: false
}
]
aboutToAppear(): void {
if (this.params) {
this.avatar = this.params.avatar || $r('app.media.Yao')
this.nickName = this.params.nickName || '好友'
}
}
// 发送消息
private sendMessage() {
if (this.inputText.trim() === '') {
return
}
const newMessage: ChatMessage = {
id: Date.now().toString(),
content: this.inputText,
time: this.getCurrentTime(),
isSelf: true
}
this.messages.push(newMessage)
this.inputText = ''
// 模拟对方回复
setTimeout(() => {
const replyMessage: ChatMessage = {
id: (Date.now() + 1).toString(),
content: '收到你的消息了!该功能已成功实现!!',
time: this.getCurrentTime(),
isSelf: false
}
this.messages.push(replyMessage)
}, 1000)
}
// 获取当前时间
private getCurrentTime(): string {
const now = new Date()
const hours = now.getHours().toString().padStart(2, '0')
const minutes = now.getMinutes().toString().padStart(2, '0')
return `${hours}:${minutes}`
}
build() {
Column() {
// 顶部导航栏
Row() {
Image($r('app.media.arrow_back'))
.width(24)
.height(24)
.margin({ left: 16 })
.onClick(() => {
router.back()
})
Image(this.avatar)
.width(32)
.height(32)
.borderRadius(16)
.margin({ left: 12 })
Text(this.nickName)
.fontSize(16)
.fontWeight(FontWeight.Bold)
.margin({ left: 8 })
.layoutWeight(1)
Button() {
Image($r('app.media.more'))
.width(24)
.height(24)
}
.width('20%')
.height(40)
.fontSize(14)
.fontColor("#666666")
.backgroundColor('#f8f8f8')
.borderRadius(20)
.margin({right:6})
.onClick(() => {
promptAction.showToast({
message:"未实现该功能!!",
duration:2000
})
})
}
.width('100%')
.height(56)
.backgroundColor('#f8f8f8')
.justifyContent(FlexAlign.SpaceBetween)
.alignItems(VerticalAlign.Center)
// 聊天消息列表
List() {
ForEach(this.messages, (message: ChatMessage) => {
ListItem() {
Row() {
if (message.isSelf) {
// 自己的消息
Column() {
Text(message.content)
.fontSize(16)
.fontColor(Color.White)
.backgroundColor('#07C160')
.borderRadius(8)
.padding(12)
.width('80%')
}
.alignItems(HorizontalAlign.End)
.layoutWeight(1)
.margin({ right: 12 })
Text(message.time)
.fontSize(12)
.fontColor(Color.Gray)
.margin({ right: 16 })
} else {
// 对方的消息
Text(message.time)
.fontSize(12)
.fontColor(Color.Gray)
.margin({ left: 16 })
Column() {
Text(message.content)
.fontSize(16)
.fontColor(Color.Black)
.backgroundColor(Color.White)
.borderRadius(8)
.padding(12)
.width('80%')
}
.alignItems(HorizontalAlign.Start)
.layoutWeight(1)
.margin({ left: 12 })
}
}
.width('100%')
.justifyContent(message.isSelf ? FlexAlign.End : FlexAlign.Start)
.margin({ top: 8, bottom: 8 })
}
})
}
.width('100%')
.layoutWeight(1)
.backgroundColor('#f5f5f5')
// 输入区域
Row() {
Button() {
Image($r('app.media.speech'))
.width(24)
.height(24)
}
.width(25)
.height(40)
.fontSize(14)
.backgroundColor('#f8f8f8')
.margin({left: 16})
.onClick(() => {
promptAction.showToast({
message:"未实现该功能!!",
duration:2000
})
})
TextInput({ placeholder: '请输入消息...' })
.width(0)
.layoutWeight(1)
.height(40)
.margin({ left: 12, right: 12 })
.backgroundColor(Color.White)
.borderRadius(20)
.onChange((value: string) => {
this.inputText = value
})
Button('发送')
.width(60)
.height(36)
.fontSize(14)
.fontColor(Color.White)
.backgroundColor('#07C160')
.margin({ right: 16 })
.onClick(() => {
this.sendMessage()
})
}
.width('100%')
.height(60)
.backgroundColor('#f8f8f8')
.justifyContent(FlexAlign.SpaceBetween)
.alignItems(VerticalAlign.Center)
}
.width('100%')
.height('100%')
.backgroundColor('#f5f5f5')
}
}
页面初始化与参数传递
- 通过@Prop params接收从列表页传递的联系人信息(ChatParams接口),包括头像和昵称。
- 在aboutToAppear生命周期钩子中初始化页面状态,确保正确显示联系人信息。
顶部导航栏
- 采用自定义布局,集成了返回按钮、联系人头像、昵称和更多操作按钮。
- 返回按钮通过router.back()实现页面回退功能。
- 更多操作按钮当前仅显示提示信息,为后续功能扩展预留接口。
消息列表展示
- 使用List组件渲染聊天记录,通过ForEach遍历messages数组。
- 实现了消息气泡的差异化显示:自己的消息:右对齐,绿色背景(#07C160),白色文字;对方消息:左对齐,白色背景,黑色文字。
- 每条消息均附带时间戳,采用灰色小字体显示。
消息发送与模拟回复
- 底部输入区域包含语音按钮、文本输入框和发送按钮。
- 发送按钮触发
sendMessage方法,将输入框内容封装为新的消息对象并添加到列表。 - 实现了自动回复机制:发送消息1秒后,系统会自动添加一条预设的回复消息,模拟真实聊天场景。
辅助功能
getCurrentTime方法自动生成当前时间,确保消息时间戳的准确性。- 语音按钮和更多选项按钮当前显示"未实现该功能"提示,保持了界面完整性。
其中发送和收到消息的功能模块实现:
- 发送消息功能

- 模拟对方回复

聊天页面展示

3.3.2. 通讯录模块 (NoteBookComponent)
该页面实现了一个功能完备的通讯录界面,提供了高效的联系人查找与管理功能,高度还原了微信通讯录的核心体验。
import { ContactGroupModel } from '../viewmodels/ContactGroupModel'
import { ContactModel } from '../viewmodels/ContactModel'
import { NavTileBar } from './NavTitleBar'
import { promptAction, router } from '@kit.ArkUI'
import { contact } from '@kit.ContactsKit'
@Component
export struct NoteBookComponent{
@State contacts:ContactGroupModel[]=[
{
title:'A',
contacts:[
{
avatar:$r('app.media.GuiGui'),
nickName:'a闺蜜٩(*´◐`*)۶'
},
{
avatar:$r('app.media.Yao'),
nickName:'安於(*^ω^*)'
},
{
avatar:$r('app.media.A_ran'),
nickName:'阿冉'
}
]
},
{
title:'B',
contacts:[
{
avatar:$r('app.media.Father'),
nickName:'爸爸'
}
]
},
{
title:'C',
contacts:[
{
avatar:$r('app.media.ChenGuo'),
nickName:'陈果'
}
]
},
{
title:'D',
contacts:[
{
avatar:$r('app.media.DiDi'),
nickName:'弟弟'
}
]
},
{
title:'E',
contacts:[
{
avatar:$r('app.media.ErJiuYe'),
nickName:'二舅爷'
}
]
},
{
title:'F',
contacts:[
{
avatar:$r('app.media.FengXiang'),
nickName:'凤祥(∗ᵒ̶̶̷̀ω˂̶́∗)੭₎'
}
]
},
{
title:'J',
contacts:[
{
avatar:$r('app.media.JiaHui'),
nickName:'佳绘'
}
]
},
{
title:'L',
contacts:[
{
avatar:$r('app.media.LiuSiTong'),
nickName:'刘思彤'
},
{
avatar:$r('app.media.LuJiaXin'),
nickName:'吕嘉欣'
}
]
},
{
title:'M',
contacts:[
{
avatar:$r('app.media.Mother'),
nickName:'妈妈'
}
]
},
{
title:'N',
contacts:[
{
avatar:$r('app.media.GrandMa'),
nickName:'奶奶'
}
]
},
{
title:'Q',
contacts:[
{
avatar:$r('app.media.Qi'),
nickName:'伊琦'
}
]
},
{
title:'R',
contacts:[
{
avatar:$r('app.media.RoXi'),
nickName:'荣茜'
}
]
},
{
title:'S',
contacts:[
{
avatar:$r('app.media.SongLiXiang'),
nickName:'宋理想'
}
]
},
{
title:'W',
contacts:[
{
avatar:$r('app.media.WenHan'),
nickName:'雯涵(* ⁰̷̴͈꒨⁰̷̴͈)'
},
{
avatar:$r('app.media.WeiBaoYi'),
nickName:'魏宝仪'
}
]
},
{
title:'X',
contacts:[
{
avatar:$r('app.media.Cao'),
nickName:'校草'
},
{
avatar:$r('app.media.Yu'),
nickName:'小鱼'
},
{
avatar:$r('app.media.XieHuiXin'),
nickName:'谢慧欣'
}
]
},
{
title:'Y',
contacts:[
{
avatar:$r('app.media.Qian'),
nickName:'颜纤儿'
},
{
avatar:$r('app.media.Han'),
nickName:'怡晗'
},
{
avatar:$r('app.media.YiRuoYu'),
nickName:'尹若羽'
}
]
}
]
@State letters:string[]=['A','B','C','D','E','F','G','H','I','J','K',
'L','M','N','O','P','Q','R','S','T','U','V','W','X','Y','Z']
private listScroller:Scroller=new Scroller()
private enterChatPage(contact: ContactModel) {
try {
router.pushUrl({
url: 'pages/ChatWrapperPage',
params: {
avatar: contact.avatar,
nickName: contact.nickName,
messages: []
}
})
console.log('从通讯录进入聊天页面成功:', contact.nickName)
} catch (error) {
console.error('从通讯录进入聊天页面失败:', error)
}
}
@Builder buildListItem(image:Resource,title:string, contact?: ContactModel){
Row(){
Image(image)
.width(40)
.height(40)
.borderRadius(5)
.margin({
left:10
})
Text(title)
.margin({
left:10
})
}.width('100%')
.height(60)
.backgroundColor(Color.White)
.alignItems(VerticalAlign.Center)
.onClick(() => {
// 只有联系人项目才添加点击事件
if (contact) {
this.enterChatPage(contact)
}
})
}
@Builder buildListItemGroupHeader(title:string){
Row(){
Text(title)
.fontSize(16)
.fontWeight(600)
.margin({
left:10
})
}.width('100%')
.height(30)
}
build() {
Stack({ alignContent: Alignment.End }) {
Column() {
NavTileBar({ title: '通讯录' })
List() {
ListItem() {
this.buildListItem($r('app.media.newfriend'), '新的朋友')
}
ListItem() {
this.buildListItem($r('app.media.onlychatfriend'), '仅聊天的朋友')
}
ListItem() {
this.buildListItem($r('app.media.groupchat'), '群聊')
}
ListItem() {
this.buildListItem($r('app.media.lable'), '标签')
}
ListItem() {
this.buildListItem($r('app.media.gzh'), '公众号')
}
}.width('100%')
.height(300)
.divider({
strokeWidth: 2,
startMargin: 10
})
List({ scroller: this.listScroller }) {
ForEach(this.contacts, (contactGroup: ContactGroupModel) => {
ListItemGroup({ header: this.buildListItemGroupHeader(contactGroup.title) })
ForEach(contactGroup.contacts, (contact: ContactModel) => {
ListItem() {
this.buildListItem(contact.avatar, contact.nickName, contact)
}
})
})
}
.animation({
curve: Curve.Linear,
duration: 1000,
playMode: PlayMode.Normal
})
.backgroundColor('#bbbab9')
.divider({
strokeWidth: 2,
startMargin: 10
})
.sticky(StickyStyle.Header)
.width('100%')
.layoutWeight(1)
}.width('100%')
.height('100%')
AlphabetIndexer({
arrayValue: this.letters,
selected: 0
})
.itemSize(20)
.margin({
top: 100
})
.onSelect((index: number) => {
const letter = this.letters[index]
for (let i = 0; i < this.contacts.length; i++) {
const contactGroup = this.contacts[i]
if (letter === contactGroup.title) {
this.listScroller.scrollToIndex(i)
}
}
})
}.height('100%')
.width('100%')
}
}
整体布局结构
- 页面采用Stack布局,主体为Column,右侧悬浮字母索引器。
- 顶部使用NavTileBar组件显示"通讯录"标题。
- 页面分为两个主要区域:上方的功能入口列表和下方的联系人分组列表。
交互与导航
点击任意联系人,通过router.pushUrl方法跳转到聊天详情页,并传递该联系人的头像和昵称参数。
字母索引功能
- 右侧集成了AlphabetIndexer字母索引器,提供A-Z的快速定位功能
- 点击索引器字母时,通过onSelect事件和listScroller.scrollToIndex方法,实现联系人列表的快速滚动定位。

联系人分组显示
- 联系人数据按首字母(A-Z)进行分组存储,每个分组包含标题和联系人列表。
- 使用ListItemGroup构建分组结构,并通过sticky(StickyStyle.Header)实现分组标题的吸顶效果。
- 每个联系人项显示头像和昵称,点击可跳转到对应的聊天页面。

通讯录页面展示

3.3.3. 发现模块 (FinderComponent)
该页面构建了微信的"发现"功能入口界面,以列表形式集中展示了各类扩展功能和服务。
import { NavTileBar } from "./NavTitleBar"
import { promptAction } from "@kit.ArkUI"
@Component
export struct FinderComponent{
@Builder
buildListItem(icon: Resource, title: string, hasArrow: boolean = true) {
Row() {
Image(icon)
.width(24)
.height(24)
.margin({ left: 16, right: 12 })
Text(title)
.fontSize(16)
.fontColor(Color.Black)
.layoutWeight(1)
if (hasArrow) {
Image($r("app.media.arrow_right"))
.width(16)
.height(16)
.margin({ right: 16 })
.fillColor('#C8C8C8')
}
}
.width('100%')
.height(50)
.backgroundColor(Color.White)
.onClick(() => {
this.showUnimplementedToast()
})
}
build() {
Column(){
NavTileBar({title:'发现'})
List() {
// 第一组:社交和内容功能
ListItem() {
this.buildListItem($r('app.media.find_moments'), '朋友圈')
}
ListItem() {
this.buildListItem($r('app.media.video'), '视频号')
}
ListItem() {
this.buildListItem($r('app.media.live'), '直播')
}
// 第二组:工具功能
ListItem() {
this.buildListItem($r('app.media.scan'), '扫一扫')
}
ListItem() {
this.buildListItem($r('app.media.music'), '听一听')
}
ListItem() {
this.buildListItem($r('app.media.look'), '看一看')
}
ListItem() {
this.buildListItem($r('app.media.search'), '搜一搜')
}
// 第三组:附近和娱乐
ListItem() {
this.buildListItem($r('app.media.nearby'), '附近')
}
ListItem() {
this.buildListItem($r('app.media.game'), '游戏')
}
ListItem() {
this.buildListItem($r('app.media.miniprogram'), '小程序')
}
}
.width('100%')
.layoutWeight(1)
.backgroundColor('#f8f7f6')
.divider({
strokeWidth: 8,
color: '#f8f7f6',
startMargin: 0,
endMargin: 0
})
}
.width('100%')
.height('100%')
.backgroundColor('#f8f7f6')
}
// 显示未实现功能的提示
private showUnimplementedToast() {
promptAction.showToast({
message: `该功能未实现!!`,
duration: 2000
})
}
}
整体布局设计
- 页面顶部采用NavTileBar组件,固定显示"发现"标题。
- 主体使用List组件垂直排列所有功能入口,背景色为浅灰色(#f8f7f6)。
功能模块分类
页面将多个功能入口划分为三个逻辑分组,覆盖了社交、内容、工具和娱乐等多个维度:
- 社交与内容核心:包括"朋友圈"、“视频号"和"直播”,是用户进行社交互动和内容消费的主要入口。
- 实用工具集:整合了"扫一扫"、“听一听”、"看一看"和"搜一搜"等高频使用的工具功能。
- 周边与娱乐:提供"附近"、"游戏"和"小程序"等本地化服务和轻应用入口。
交互逻辑
- 所有功能项均绑定了统一的点击事件,触发showUnimplementedToast方法。
- 点击后会弹出"该功能未实现!!"的提示信息,持续时间为2秒。
- 这种设计保持了界面的完整性,为后续功能开发预留了清晰的接口。

- 显示未实现提示

发现页面展示

3.3.4. 个人中心模块 (MeComponent)
该页面是应用的用户个人中心,集成了个人信息展示、状态管理和各项服务的入口,是用户进行个人设置和查看的核心界面。
import { NavTileBar } from "./NavTitleBar"
import { promptAction, router } from "@kit.ArkUI";
// 定义用户信息接口
interface UserInfo {
avatar: Resource;
nickname: string;
wechatId: string;
qrCode: Resource;
}
@Component
export struct MeComponent{
@State userInfo: UserInfo = {
avatar: $r('app.media.Yao'),
nickname: '安於',
wechatId: 'Cmy66666888888',
qrCode: $r("app.media.qr_icon")
}
@Builder
buildFunctionItem(icon: Resource, title: string, badge?: string) {
Row() {
Row() {
Image(icon)
.width(24)
.height(24)
.margin({ right: 12 })
Text(title)
.fontSize(16)
.fontColor(Color.Black)
.fontWeight(500)
}
.layoutWeight(1)
Row() {
if (badge) {
Text(badge)
.fontSize(12)
.fontColor(Color.Red)
.margin({ right: 8 })
}
Image($r('app.media.arrow_right'))
.width(16)
.height(16)
.fillColor('#C8C8C8')
}
}
.width('100%')
.height(50)
.padding({ left: 16, right: 16 })
.backgroundColor(Color.White)
.onClick(() => {
this.showUnimplementedToast()
})
}
build() {
Column(){
NavTileBar({title:'我'})
// 用户信息区域
Column() {
Row() {
// 头像
Image(this.userInfo.avatar)
.width(64)
.height(64)
.borderRadius(5)
.margin({ left: 16, right: 12 })
// 用户信息
Column() {
Text(this.userInfo.nickname)
.fontSize(18)
.fontColor(Color.Black)
.fontWeight(FontWeight.Bold)
.margin({ bottom: 4 })
Text('微信号: ' + this.userInfo.wechatId)
.fontSize(14)
.fontColor('#666666')
.margin({top:6})
}
.alignItems(HorizontalAlign.Start)
.layoutWeight(1)
// 二维码图标
Image(this.userInfo.qrCode)
.width(24)
.height(24)
.margin({ right: 16 })
.onClick(() => {
// 显示二维码弹窗
this.showQRCode()
})
// 箭头图标
Image($r('app.media.arrow_right'))
.width(16)
.height(16)
.fillColor('#C8C8C8')
.margin({ right: 16 })
.onClick(() => {
// 点击箭头跳转到个人资料页面
this.enterProfilePage()
})
}
.width('100%')
.height(90)
.padding({ top: 16, bottom: 16 })
.backgroundColor(Color.White)
// 状态栏
Row() {
Button('+ 状态')
.fontSize(14)
.fontColor(Color.Black)
.backgroundColor(Color.Transparent)
.border({ width: 1, color: '#E5E5E5' })
.borderRadius(40)
.padding({ left: 16, right: 16, top: 6, bottom: 6 })
.margin({ left: 87, right: 8 })
.onClick(() => {
promptAction.showToast({
message:"功能未实现!!",
duration:2000
})
})
Image($r('app.media.refresh'))
.width(20)
.height(20)
.fillColor('#666666')
.margin({ right: 16 })
.onClick(() => {
promptAction.showToast({
message:"已刷新!!",
duration:2000
})
})
}
.width('100%')
.height(50)
.backgroundColor(Color.White)
.justifyContent(FlexAlign.Start)
.alignItems(VerticalAlign.Center)
}
// 功能列表
List() {
ListItem() {
this.buildFunctionItem($r('app.media.service'), '服务')
}
ListItem() {
this.buildFunctionItem($r('app.media.favorite'), '收藏')
}
ListItem() {
this.buildFunctionItem($r('app.media.moments'), '朋友圈')
}
ListItem() {
this.buildFunctionItem($r('app.media.card_package'), '卡包')
}
ListItem() {
this.buildFunctionItem($r('app.media.emoji'), '表情')
}
ListItem() {
this.buildFunctionItem($r('app.media.settings'), '设置')
}
}
.width('100%')
.layoutWeight(1)
.backgroundColor('#f8f7f6')
.divider({
strokeWidth: 8,
color: '#f8f7f6',
startMargin: 0,
endMargin: 0
})
}
.width('100%')
.height('100%')
.backgroundColor('#f8f7f6')
}
private showQRCode() {
this.getUIContext().getRouter().pushUrl({
url: 'pages/QRCodePage',
params: {
nickname: this.userInfo.nickname,
wechatId: this.userInfo.wechatId,
avatar: this.userInfo.avatar
}
})
}
// 显示未实现功能的提示
private showUnimplementedToast() {
promptAction.showToast({
message: `该功能未实现!!`,
duration: 2000
})
}
// 新增方法:跳转到个人资料页面
private enterProfilePage() {
try {
router.pushUrl({
url: 'pages/ProfilePage',
params: {
avatar: this.userInfo.avatar,
name: this.userInfo.nickname,
wechatId: this.userInfo.wechatId,
gender: '女',
region: '郑州 中原区',
phone: '138****8888',
signature: '不是成魔就是成功೭(˵ˉꇴˉ˵)౨ ',
ringtone: '勋章',
address: '郑州市中原区中原中路41号',
invoiceTitle: '个人',
wechatBeans: '666'
}
})
console.log('跳转到个人资料页面成功')
} catch (error) {
console.error('跳转到个人资料页面失败:', error)
}
}
}
用户信息展示区
- 页面顶部显著展示用户的个人资料,包括64x64像素的圆角头像、昵称和微信号。
- 通过UserInfo接口结构化管理用户数据,确保了数据的一致性和可维护性。
- 在用户信息右侧集成了二维码和箭头两个交互图标,分别用于展示个人二维码和跳转到详细资料页。

状态栏功能
- 在用户信息下方提供了一个轻量级的状态栏,包含"+ 状态"按钮和刷新图标。
- "+ 状态"按钮采用边框设计,为用户发布个人状态预留了功能入口。
- 刷新图标点击后会提示"已刷新",为动态内容更新提供了即时反馈。
功能列表入口
- 主体部分采用List组件,集成了"服务"、“收藏”、“朋友圈”、“卡包”、"表情"和"设置"六个核心功能入口。
- 通过@Builder装饰器创建了可复用的buildFunctionItem方法,统一了列表项的样式和交互逻辑。
- 每个列表项均采用图标+文字的组合形式,右侧配有灰色箭头指示符,保持了微信的设计语言。
页面导航与交互
- 实现了两个关键的页面跳转功能:点击二维码图标,通过router.pushUrl跳转到QRCodePage,并传递用户基本信息;点击箭头图标,跳转到ProfilePage个人资料页,并传递更详细的用户资料。
- 所有其他功能项点击后均显示"该功能未实现"的提示,为后续开发预留了清晰的扩展点。
我的页面展示

3.3.5个人资料页面(ProfilePage)
该页面是一个用于展示和查看用户详细个人信息的界面。
import { router } from '@kit.ArkUI'
interface ProfileInfo {
avatar: Resource
name: string
gender: string
region: string
phone: string
wechatId: string
signature: string
ringtone: string
address: string
invoiceTitle: string
wechatBeans: string
}
@Entry
@Component
struct ProfilePage {
@State profileInfo: ProfileInfo = {
avatar: $r('app.media.Yao'),
name: '安於',
gender: '女',
region: '郑州 中原区',
phone: '138****8888',
wechatId: 'Cmy66666888888',
signature: '不是成魔就是成功೭(˵ˉꇴˉ˵)౨ ',
ringtone: '勋章',
address: '郑州市中原区中原中路41号',
invoiceTitle: '个人',
wechatBeans: '666'
}
aboutToAppear(): void {
try {
const params:ProfileInfo = router.getParams() as ProfileInfo
if (params) {
this.profileInfo = {
avatar: params.avatar || $r('app.media.Yao'),
name: params.name || '安於',
gender: params.gender || '女',
region: params.region || '郑州 中原区',
phone: params.phone || '138****8888',
wechatId: params.wechatId || 'Cmy66666888888',
signature: params.signature || '不是成魔就是成功೭(˵ˉꇴˉ˵)౨ ',
ringtone: params.ringtone || '勋章',
address: params.address || '郑州市中原区中原中路41号',
invoiceTitle: params.invoiceTitle || '个人',
wechatBeans: params.wechatBeans || '666'
}
}
} catch (error) {
console.error('获取个人资料失败:', error)
}
}
@Builder ProfileItem(title: string, value: string, showArrow: boolean = true) {
Row() {
Text(title)
.fontSize(16)
.fontColor('#333333')
.width(100)
Text(value)
.fontSize(16)
.fontColor('#666666')
.layoutWeight(1)
.textAlign(TextAlign.End)
.margin({ right: 8 })
.maxLines(1)
.textOverflow({ overflow: TextOverflow.Ellipsis })
if (showArrow) {
Image($r('app.media.arrow_right'))
.width(16)
.height(16)
.fillColor('#c8c8c8')
.margin({ right: 16 })
}
}
.width('100%')
.height(50)
.backgroundColor(Color.White)
.padding({ left: 16 })
.justifyContent(FlexAlign.SpaceBetween)
}
// 控制头像弹窗显示状态
@State showAvatarDialog: boolean = false
// 头像弹窗构建器
@Builder AvatarDialog() {
Column() {
// 弹窗内容
Column() {
// 关闭按钮
Row() {
Blank()
Image($r('app.media.close'))
.width(24)
.height(24)
.fillColor('#666666')
.onClick(() => {
this.showAvatarDialog = false
})
}
.width('100%')
.margin({ bottom: 20 })
// 放大的头像
Image(this.profileInfo.avatar)
.width(280)
.height(280)
.borderRadius(8)
.objectFit(ImageFit.Contain)
// 用户信息
Text(this.profileInfo.name)
.fontSize(20)
.fontWeight(FontWeight.Bold)
.fontColor('#333333')
.margin({ top: 20, bottom: 8 })
Text('微信号: ' + this.profileInfo.wechatId)
.fontSize(14)
.fontColor('#666666')
.margin({ bottom: 30 })
}
.padding(20)
.backgroundColor(Color.White)
.borderRadius(12)
.margin({left:20,right:20})
}
.width('100%')
.height('100%')
.backgroundColor('rgba(0, 0, 0, 0.5)')
.justifyContent(FlexAlign.Center)
.onClick(() => {
// 点击背景关闭弹窗
this.showAvatarDialog = false
})
}
build() {
Column() {
// 顶部导航栏
Row() {
Image($r('app.media.arrow_back'))
.width(24)
.height(24)
.margin({ left: 16 })
.onClick(() => {
router.back()
})
Text('个人资料')
.fontSize(18)
.fontWeight(FontWeight.Bold)
.margin({ left: 112 })
.layoutWeight(1)
}
.width('100%')
.height(56)
.backgroundColor('#f8f8f8')
.justifyContent(FlexAlign.Start)
.alignItems(VerticalAlign.Center)
Scroll() {
Column() {
// 头像区域
Row() {
Text('头像')
.fontSize(16)
.fontColor('#333333')
.width(100)
Image(this.profileInfo.avatar)
.width(60)
.height(60)
.borderRadius(5)
.margin({ left: 126 })
.onClick(()=>{
// 点击头像显示弹窗
this.showAvatarDialog = true
})
Image($r('app.media.arrow_right'))
.width(16)
.height(16)
.fillColor('#c8c8c8')
.margin({ right: 16 })
}
.width('100%')
.height(80)
.backgroundColor(Color.White)
.padding({ left: 16 })
.justifyContent(FlexAlign.SpaceBetween)
.alignItems(VerticalAlign.Center)
// 分割线
Divider()
.color('#e5e5e5')
.strokeWidth(1)
.margin({ left: 116 })
// 基本信息
this.ProfileItem('名字', this.profileInfo.name)
Divider()
.color('#e5e5e5')
.strokeWidth(1)
.margin({ left: 16 })
this.ProfileItem('性别', this.profileInfo.gender)
Divider()
.color('#e5e5e5')
.strokeWidth(1)
.margin({ left: 16 })
this.ProfileItem('地区', this.profileInfo.region)
Divider()
.color('#e5e5e5')
.strokeWidth(1)
.margin({ left: 16 })
this.ProfileItem('手机号', this.profileInfo.phone)
// 微信号区域
Column() {
this.ProfileItem('微信号', this.profileInfo.wechatId)
Divider()
.color('#e5e5e5')
.strokeWidth(1)
.margin({ left: 16 })
// 我的二维码
Row() {
Text('我的二维码')
.fontSize(16)
.fontColor('#333333')
.width(100)
Image($r('app.media.arrow_right'))
.width(16)
.height(16)
.fillColor('#c8c8c8')
.margin({ right: 16 })
}
.width('100%')
.height(50)
.backgroundColor(Color.White)
.padding({ left: 16 })
.justifyContent(FlexAlign.SpaceBetween)
.alignItems(VerticalAlign.Center)
.onClick(() => {
this.getUIContext().getRouter().pushUrl({
url:'pages/QRCodePage',
params: {
nickname: this.profileInfo.name,
wechatId: this.profileInfo.wechatId,
avatar: this.profileInfo.avatar
}
})
})
}
.margin({ top: 10 })
// 拍一拍
Column() {
Row() {
Text('拍一拍')
.fontSize(16)
.fontColor('#333333')
.width(100)
Text('变成气鼓鼓的小河豚🐡 ')
.fontSize(16)
.fontColor('#666666')
.layoutWeight(1)
.textAlign(TextAlign.End)
.margin({ right: 8 })
Image($r('app.media.arrow_right'))
.width(16)
.height(16)
.fillColor('#c8c8c8')
.margin({ right: 16 })
}
.width('100%')
.height(50)
.backgroundColor(Color.White)
.padding({ left: 16 })
.justifyContent(FlexAlign.SpaceBetween)
.alignItems(VerticalAlign.Center)
}
.margin({ top: 10 })
Divider()
.color('#e5e5e5')
.strokeWidth(1)
.margin({ left: 116 })
// 签名
this.ProfileItem('签名', this.profileInfo.signature)
// 更多设置
Column() {
this.ProfileItem('来电铃声', this.profileInfo.ringtone)
Divider()
.color('#e5e5e5')
.strokeWidth(1)
.margin({ left: 16 })
this.ProfileItem('我的地址', this.profileInfo.address)
Divider()
.color('#e5e5e5')
.strokeWidth(1)
.margin({ left: 16 })
this.ProfileItem('我的发票抬头', this.profileInfo.invoiceTitle)
Divider()
.color('#e5e5e5')
.strokeWidth(1)
.margin({ left: 16 })
this.ProfileItem('微信豆', this.profileInfo.wechatBeans)
}
.margin({ top: 10 })
}
}
.layoutWeight(1)
.backgroundColor('#f5f5f5')
}
.width('100%')
.height('100%')
.backgroundColor('#f5f5f5')
// 绑定弹窗
.bindContentCover(this.showAvatarDialog, this.AvatarDialog(), {
modalTransition: ModalTransition.NONE,
backgroundColor: Color.Transparent
})
}
}
数据管理与初始化
- 通过ProfileInfo接口结构化定义了个人资料的数据模型,涵盖头像、姓名、性别、地区、联系方式等十余个字段。
- 在aboutToAppear生命周期中,通过router.getParams()接收并解析上级页面传递的参数,实现了页面间的数据同步。
界面布局与结构
- 页面顶部为自定义导航栏,包含返回按钮和"个人资料"标题,符合应用整体设计规范。
- 主体内容采用Scroll组件包裹,确保在信息量较大时用户可以滚动查看,提升了页面的适应性。
核心交互功能
- 头像预览:点击头像可触发一个全屏遮罩弹窗,展示280x280像素的放大头像,用户可通过点击关闭按钮或背景来关闭弹窗。
- 二维码跳转:点击"我的二维码"选项,通过router.pushUrl方法跳转到二维码展示页,并传递必要的用户信息。
- 导航返回:顶部返回按钮通过router.back()实现页面回退。
个人资料界面展示

头像点击放大展示

3.3.6二维码页面(QRCodePage)
该页面是一个用于展示用户个人二维码的独立界面,为其他用户通过扫码添加好友提供了便捷的入口。
import { promptAction, router } from '@kit.ArkUI'
@Entry
@Component
struct QRCodePage {
@State params:object=this.getUIContext().getRouter().getParams()
@State nickname: string = ''
@State wechatId: string = ''
@State avatar: Resource = $r('app.media.Yao')
aboutToAppear(): void {
const params = router.getParams() as Record<string, Object>
if (params) {
this.nickname = (params['nickname'] as string) || ''
this.wechatId = (params['wechatId'] as string) || ''
this.avatar = (params['avatar'] as Resource) || $r('app.media.Yao')
}
}
build() {
Column() {
// 顶部返回栏
Row() {
Image($r('app.media.arrow_back'))
.width(24)
.height(24)
.margin({ left: 16 })
.onClick(() => {
router.back()
})
Text('我的二维码')
.fontWeight(FontWeight.Bold)
.fontSize(16)
.layoutWeight(1)
.textAlign(TextAlign.Center)
Row().width(24).height(24).margin({ right: 16 })
}
.backgroundColor("#f8f7f6")
.width('100%')
.height(50)
.justifyContent(FlexAlign.SpaceBetween)
.alignItems(VerticalAlign.Center)
.backgroundColor('#f8f7f6')
Row(){
// 用户头像
Image(this.avatar)
.width(80)
.height(80)
.borderRadius(5)
.margin({ top: 40 })
// 昵称
Text(this.nickname)
.fontSize(20)
.fontWeight(FontWeight.Bold)
.fontColor(Color.Black)
.margin({ top: 14,left:10})
}
.width('100%')
.justifyContent(FlexAlign.Start)
.margin({left:112})
// 二维码图片(使用预置图片)
Image($r('app.media.qr_code'))
.width(250)
.height(250)
.borderRadius(5)
.margin({ top: 30 })
Text("扫一扫上面的二维码图案,加我为朋友")
.fontSize(10)
.fontColor("#bbbab9")
.margin({top:30})
Row(){
// 扫一扫按钮
Button('扫一扫')
.width('30%')
.height(40)
.fontSize(16)
.fontColor("#0047AB")
.backgroundColor(Color.White)
.borderRadius(20)
.margin({ top: 40 })
.onClick(() => {
promptAction.showToast({
message:"未实际该功能!!",
duration:2000
})
})
Button('换个样式')
.width('30%')
.height(40)
.fontSize(16)
.fontColor("#0047AB")
.backgroundColor(Color.White)
.borderRadius(20)
.margin({ top: 40 })
.onClick(() => {
promptAction.showToast({
message:"未实际该功能!!",
duration:2000
})
})
Button('保存图片')
.width('30%')
.height(40)
.fontSize(16)
.fontColor("#0047AB")
.backgroundColor(Color.White)
.borderRadius(20)
.margin({ top: 40 })
.onClick(() => {
promptAction.showToast({
message:"未实际该功能!!",
duration:2000
})
})
}
}
.width('100%')
.height('100%')
.backgroundColor(Color.White)
.alignItems(HorizontalAlign.Center)
}
}
页面初始化与数据接收
- 页面通过router.getParams()方法接收从上一级页面(如个人中心)传递的用户信息。
- 在aboutToAppear生命周期钩子中,解析并初始化用户的昵称、微信号和头像资源。
二维码展示
- 中央展示一个250x250像素的二维码图片(当前为预置资源)。
- 二维码下方配有引导性提示文字"扫一扫上面的二维码图案,加我为朋友",字体为灰色,字号较小,起到了辅助说明的作用。
功能操作区
- 页面底部水平排列了三个核心功能按钮:“扫一扫”、“换个样式"和"保存图片”。
- 所有按钮均采用白色背景、蓝色文字的轻量化设计,宽度均等,保持了界面的对称性。
- 当前所有按钮点击后均显示"未实际该功能"的提示,为后续功能扩展(如调用相机、切换二维码样式、保存到相册)预留了清晰的接口。
二维码界面展示

3.4数据模型架构
3.4.1基础模型 (ContactModel.ets)
export interface ContactModel{
avatar:Resource
nickName:string
}
3.4.2分组模型 (ContactGroupModel.ets)
import { ContactModel } from "./ContactModel";
export interface ContactGroupModel{
title:string,
contacts:ContactModel[]
}
3.4.3消息模型 (MessageModel.ets)
import { ContactModel } from "./ContactModel"
export interface MessageModel extends ContactModel{
message:string
time:string
}
3.4.4模型关系图

3.5页面跳转关系
3.5.1完整的用户流程

3.5.2参数传递示例
- 从通讯录跳转到聊天
private enterChatPage(contact: ContactModel) {
try {
router.pushUrl({
url: 'pages/ChatWrapperPage',
params: {
avatar: contact.avatar,
nickName: contact.nickName,
messages: []
}
})
console.log('从通讯录进入聊天页面成功:', contact.nickName)
} catch (error) {
console.error('从通讯录进入聊天页面失败:', error)
}
}
- 在ChatWrapperPage中接收参数
aboutToAppear(): void {
// 获取路由传递的参数
const routerParams = router.getParams() as ChatParams
if (routerParams) {
this.params.avatar = routerParams.avatar || $r('app.media.Yao')
this.params.nickName = routerParams.nickName || '好友'
this.params.messages = routerParams.messages || []
}
console.log('ChatWrapperPage aboutToAppear, params:', this.params)
}
3.6通用组件详解
3.6.1导航栏组件 (NavTitleBar.ets)
该组件是一个高度可复用的自定义导航栏,为应用内多个页面提供了统一且功能丰富的顶部导航体验。
import { promptAction, router } from "@kit.ArkUI"
@Component
export struct NavTileBar{
@Prop title:string
@State showMenu: boolean = false // 控制菜单显示状态
build() {
Stack() {
//导航栏
Row(){
Image($r('app.media.arrow_back'))
.width(24)
.height(24)
.margin({ left: 16 })
.onClick(() => {
// 返回按钮点击事件
router.back()
})
Text(this.title)
.fontWeight(800)
.fontSize(16)
.layoutWeight(1)
.textAlign(TextAlign.Center)
Row() {
Image($r('app.media.find'))
.width(24)
.height(24)
.margin({ right: 16 })
.onClick(() => {
// 搜索按钮点击事件 - 跳转到搜索页面
router.pushUrl({
url: 'pages/SearchPage'
})
})
// 加号按钮 - 只有点击这个按钮才显示菜单
Image($r('app.media.add_top'))
.width(24)
.height(24)
.margin({ right: 16 })
.bindMenu([
{
value: '发起群聊',
icon: $r('app.media.group_chat'),
action: () => {
this.showUnimplementedToast('发起群聊')
}
},
{
value: '添加朋友',
icon: $r('app.media.add_friend'),
action: () => {
this.showUnimplementedToast('添加朋友')
}
},
{
value: '扫一扫',
icon: $r('app.media.Nav_scan'),
action: () => {
this.showUnimplementedToast('扫一扫')
}
},
{
value: '收付款',
icon: $r('app.media.payment'),
action: () => {
this.showUnimplementedToast('收付款')
}
}
])
}
}
.width('100%')
.height(50)
.justifyContent(FlexAlign.SpaceBetween)
.alignItems(VerticalAlign.Center)
.backgroundColor('#f8f7f6')
}
.width('100%')
.height(50)
}
// 显示未实现功能的提示
private showUnimplementedToast(featureName: string) {
promptAction.showToast({
message: `${featureName}功能未实现!!`,
duration: 2000
})
}
}
核心布局与设计
- 采用Row水平布局,整体高度为50像素,背景色为浅灰色(#f8f7f6),符合微信的视觉风格。
- 导航栏从左至右依次为:返回按钮、标题文本、功能图标组,结构清晰,符合用户操作习惯。
交互功能实现
- 返回功能:左侧返回按钮通过router.back()方法实现页面回退,是导航的基础功能。
- 搜索功能:右侧的搜索图标点击后可跳转到SearchPage,为用户提供全局搜索入口。
- 扩展菜单:通过bindMenu属性为加号按钮绑定了一个下拉菜单,集成了四个核心快捷操作。
扩展菜单功能
菜单项以列表形式展示,每个选项包含图标和文字,点击后触发相应动作:
- 发起群聊:用于创建新的群组对话。
- 添加朋友:提供添加新联系人的入口。
- 扫一扫:调用扫码功能。
- 收付款:进入支付相关界面。
当前所有菜单项点击后均显示"功能未实现"的提示,为后续功能开发预留了清晰的接口。
扩展菜单界面展示

3.6.2搜索组件 (SearchComponent.ets)
该组件是一个功能完备的搜索界面,集成了搜索输入、历史记录管理和结果展示等功能,为用户提供了便捷的站内搜索体验。
// 定义搜索结果接口 - 移到组件外部
interface SearchResult {
avatar: Resource
name: string
description: string
type: string
}
@Component
export struct SearchComponent {
@Prop onClose: () => void
@State searchText: string = ''
@State searchHistory: string[] = ['朋友圈', '文章', '公众号', '小程序', '音乐', '表情']
@State searchResults: SearchResult[] = []
build() {
Column() {
// 搜索栏
Row() {
Image($r('app.media.arrow_back'))
.width(24)
.height(24)
.margin({ left: 16 })
.onClick(() => {
if (this.onClose) {
this.onClose()
}
})
// 搜索输入框
Row() {
Image($r('app.media.search_icon'))
.width(20)
.height(20)
.margin({ left: 12, right: 8 })
.fillColor('#999999')
TextInput({ placeholder: '搜索' })
.width('100%')
.height(40)
.backgroundColor(Color.Transparent)
.fontSize(16)
.fontColor('#333333')
.onChange((value: string) => {
this.searchText = value
// 模拟搜索逻辑
if (value) {
this.searchResults = [
{
avatar: $r('app.media.Yao'),
name: '安於(*^ω^*)',
description: '微信号: Cmy66666888888',
type: 'friend'
},
{
avatar: $r('app.media.GuiGui'),
name: 'a闺蜜٩(*´◐`*)۶',
description: '微信号: lisi456',
type: 'friend'
}
]
} else {
this.searchResults = []
}
})
}
.width('70%')
.height(36)
.backgroundColor('#F5F5F5')
.borderRadius(18)
.margin({ left: 12, right: 16 })
}
.width('100%')
.height(56)
.backgroundColor('#FFFFFF')
.justifyContent(FlexAlign.Start)
.alignItems(VerticalAlign.Center)
// 搜索内容区域
if (this.searchText === '') {
// 搜索历史
Column() {
Row() {
Text('搜索历史')
.fontSize(14)
.fontColor('#999999')
.margin({ left: 16 })
Blank()
Image($r('app.media.delete'))
.width(16)
.height(16)
.margin({ right: 16 })
.fillColor('#999999')
.onClick(() => {
// 清空搜索历史
this.searchHistory = []
})
}
.width('100%')
.height(40)
.justifyContent(FlexAlign.SpaceBetween)
.alignItems(VerticalAlign.Center)
// 历史标签
Flex({ wrap: FlexWrap.Wrap, justifyContent: FlexAlign.Start }) {
ForEach(this.searchHistory, (item: string) => {
Text(item)
.fontSize(14)
.fontColor('#333333')
.padding({ left: 12, right: 12, top: 6, bottom: 6 })
.margin({ right: 8, bottom: 8 })
.backgroundColor('#F5F5F5')
.borderRadius(15)
.onClick(() => {
this.searchText = item
})
})
}
.width('100%')
.padding({ left: 16, right: 16 })
}
.width('100%')
.backgroundColor('#FFFFFF')
.margin({ top: 1 })
} else {
// 搜索结果
Column() {
ForEach(this.searchResults, (item: SearchResult) => {
Row() {
Image(item.avatar)
.width(40)
.height(40)
.borderRadius(4)
.margin({ left: 16, right: 12 })
Column() {
Text(item.name)
.fontSize(16)
.fontColor('#333333')
.fontWeight(500)
Text(item.description)
.fontSize(12)
.fontColor('#999999')
.margin({ top: 2 })
}
.layoutWeight(1)
.alignItems(HorizontalAlign.Start)
if (item.type === 'friend') {
Text('添加')
.fontSize(14)
.fontColor('#07C160')
.padding({ left: 12, right: 12, top: 6, bottom: 6 })
.borderRadius(4)
.border({ width: 1, color: '#07C160' })
.margin({ right: 16 })
}
}
.width('100%')
.height(64)
.backgroundColor('#FFFFFF')
.justifyContent(FlexAlign.Start)
.alignItems(VerticalAlign.Center)
.onClick(() => {
// 点击搜索结果
})
})
}
.width('100%')
.backgroundColor('#FFFFFF')
}
Blank()
}
.width('100%')
.height('100%')
.backgroundColor('#F8F8F8')
}
}
搜索栏设计
- 顶部搜索栏集成了返回、搜索图标和输入框,采用圆角背景设计,视觉上与微信搜索功能保持一致。
- 输入框具有实时响应能力,通过onChange事件监听文本变化,动态触发搜索逻辑。
- 返回按钮通过@Prop onClose回调函数与父组件通信,实现了灵活的页面关闭控制。
搜索历史功能
- 在未输入搜索关键词时,界面展示用户的搜索历史记录。
- 历史记录以灵活的标签形式(Flex布局)展示,用户可点击任意标签快速填充搜索框。
- 提供了清空历史功能,点击删除图标即可清空所有历史记录,操作简单直观。
搜索结果展示
- 当用户输入内容时,界面切换至搜索结果视图。
- 每个结果项显示用户头像、昵称和微信号,并根据类型(如好友)提供相应的操作按钮(如"添加")。
搜索页面展示

四、软件图标更改步骤
准备图标素材
- 找一张你想要的图标图片(PNG格式,建议512×512像素)。
- 准备两张图片:一张背景图(如绿色背景)和一张前景图(如白色图标)。
替换图标资源文件
- 将背景图命名为 background.png,替换到 entry/src/main/resources/base/media/ 目录。
- 将前景图命名为 foreground.png,替换到同一目录。
修改配置文件
- 打开
entry/src/main/module.json5文件。 - 找到
abilities下的icon字段,确保值为$media:layered_image。
"icon": "$media:layered_image"
- 打开 entry/src/main/resources/base/media/layered_image.json 文件。
- 确保配置指向你的背景图和前景图。
{
"layered-image":
{
"background" : "$media:background",
"foreground" : "$media:foreground"
}
}
运行后的图标界面

五、学习路径建议
5.1初学者路径
- 理解项目结构:先搞清楚文件组织方式。
- 学习数据模型:掌握ContactModel、MessageModel等核心结构。
- 熟悉页面流程:从PreLogin到Index的完整跳转流程。
- 掌握组件使用:学会使用NavTitleBar等通用组件的复用方法。
- 实践页面跳转:理解router.pushUrl的跨页面使用。
5.2进阶学习方向
- 动画效果:为页面切换添加平滑过渡动画。
- 网络请求:对接真实后端 API,实现数据动态加载。
- 数据持久化:使用本地存储(如 Preferences)保存用户数据。
- 性能优化:优化列表渲染和内存占用,提升流畅度。
- 多语言支持:通过 i18n 实现国际化,适配不同语言用户。
六、开发环境配置
6.1必需工具
- DevEco Studio:HarmonyOS 官方集成开发环境。
- HarmonyOS SDK:包含开发所需的核心库和工具。
- 模拟器或真机:用于应用测试和调试。
6.2项目导入步骤
- 下载并安装DevEco Studio。
- 创建新的HarmonyOS项目。
打开DevEco Studio,选择File > New > Create Project。
选择模板Empty Ability,点击Next。
填写项目信,点击Finish,等待项目创建完成。
- 将项目文件复制到对应目录。
- 配置资源文件(图片、字符串等)。
- 点击运行按钮,进行调试。
七、功能实现状态
7.1已实现功能
- 完整的登录流程(包括验证逻辑)。
- 底部 Tab 导航(首页、聊天、通讯录等)。
- 聊天消息的模拟发送与接收。
- 通讯录字母索引(按姓名首字母分组)。
- 个人信息展示(头像、昵称、微信号等)。
- 搜索功能(模拟搜索历史和结果)。
- 二维码展示(个人二维码页面)。
7.2待完善功能
- 朋友圈功能(发布、浏览、点赞)。
- 视频号模块(短视频浏览与播放)。
- 真实网络通信(替换模拟数据为真实 API)。
- 数据持久化(保存用户设置和聊天记录)。
- 消息推送(实时通知新消息)。
- 文件传输(图片、文档等附件发送)。
八、项目总结
8.1学习成果
通过这个项目,你将掌握:
- 掌握 HarmonyOS ArkUI 框架的基本用法。
- 理解组件化开发思想,提升代码复用性。
- 掌握移动应用 UI 设计的基本原则(如布局、交互)。
- 熟悉状态管理和事件处理机制。
- 实现页面导航和参数传递。
8.2项目价值
- 完整用户体验:覆盖从登录到核心功能的完整流程。
- 规范代码结构:模块化设计便于维护和扩展。
- 丰富学习资源:详细注释和文档降低学习门槛。
- 实用开发技巧:可直接迁移到其他项目。
8.3后续发展
掌握了这个项目后,你可以:
- 开发更复杂的社交应用(如多端同步、实时音视频)。
- 在GitHub上分享自己的作品。
- 为开源社区贡献代码。
九、技术支持
请确保你的DevEco Studio和SDK版本与项目要求兼容。如果在学习过程中遇到问题,建议:
- 仔细阅读代码注释,理解设计意图。
- 查阅 HarmonyOS 官方文档(如 ArkUI 开发指南)。
- 在开发者社区提问(如 HarmonyOS 论坛)。
- 与同学和朋友讨论交流,集思广益。
十、参考文献标注
参考来源:主页面和通讯录页面的部分设计灵感来源于哔哩哔哩平台玄逸991的主页,链接如下:https://www.bilibili.com/video/BV1qf421f7De/?spm_id_from=333.1391.0.0&vd_source=daba134500045ebcd16a9a03ddee4b0f
源码下载
为了方便学习和对照,本项目的完整源码已上传到了GitCode,链接如下:
项目链接:https://gitcode.com/AnYuChen/MyHomeWork3/tree/master/FinalApplication
更多推荐


所有评论(0)