25. 我的页面、Web 嵌入与项目总结
本章导读
「我的」页面是应用的个人中心入口,承载用户信息、积分管理、系统设置等功能。本章将详解 MineView 的登录状态管理、WebPage 的网页嵌入、SettingPage 与 AboutPage 的设置体系,并对整个项目的架构设计与最佳实践进行全面总结。
25.1 MineView 个人中心
用户信息展示
MineView 展示头像、昵称和登录状态,未登录时点击头像触发登录:
typescript
// MineView.ets
@Component
export struct MineView {
@State isLoggedIn: boolean = false;
@State userInfo: string = '未登录';
@State userNickname: string = '柚兔';
@State avatar: Resource | string = $r('app.media.ic_avatar');
aboutToAppear(): void {
this.updateLoginState()
}
build() {
Column() {
Row({ space: 10 }) {
Image(this.avatar).width(48)
.clip(true)
.borderRadius(30)
.onClick(() => {
if (!this.isLoggedIn) {
this.login()
}
})
Column({ space: 8 }) {
Text(this.userInfo).fontSize(16).fontWeight(FontWeight.Bold)
Text('书山有路勤为径').fontColor($r('app.color.color_sub_text')).fontSize(12)
}.layoutWeight(1).alignItems(HorizontalAlign.Start)
Image($r('app.media.ic_setting')).width(32)
.onClick(() => {
this.pageContext.openPage({
routerName: 'SettingPage',
onReturn: async (data) => {
this.updateLoginState()
}
}, true);
})
}
.backgroundColor($r('app.color.color_card'))
.borderRadius(5).padding(12)
}
}
}
登录流程
登录通过 BackupManager 华为账号服务实现:
typescript
login() {
const backupManager = BackupManager.getInstance();
if (!BackupManagerService.getInstance().isBackupManagerInitialized()) {
promptAction.showToast({ message: '等待初始化完成' });
return;
}
this.isLoggedIn = UserInfoManager.isLoggedIn();
if (this.isLoggedIn) return;
try {
backupManager.login();
setTimeout(() => {
this.updateLoginState();
}, 1000);
} catch (error) {
console.error('登录失败:', error);
}
}
登录状态更新
typescript
private updateLoginState() {
this.isLoggedIn = UserInfoManager.isLoggedIn();
if (this.isLoggedIn) {
const userInfoData = UserInfoManager.getUserInfo();
this.userInfo = `${userInfoData?.nickName || '未登录'}`;
this.avatar = userInfoData?.avatarUri || $r('app.media.ic_avatar')
} else {
this.userInfo = '未登录';
this.avatar = $r('app.media.ic_avatar')
}
}
UserInfoManager 封装了华为账号的用户信息读取,isLoggedIn() 判断登录状态,getUserInfo() 获取昵称和头像 URI。
CardItem 列表导航
MineView 使用通用 CardItem 组件构建功能列表:
typescript
Column({ space: 12 }) {
CardItem({ name: '我的积分' }).onClick(() => {
this.pageContext.openPage({ routerName: 'ScorePage' }, true);
})
CardItem({ name: '反馈建议' }).onClick(() => {
this.pageContext.openPage({
param: {
title: '反馈建议',
url: 'https://ncn1rpfvd5vb.feishu.cn/share/base/form/shrcnwljf1ptBZVyKfmWH3eS6Ih',
} as ResultParams,
routerName: 'WebPage',
}, true);
})
}
通过 ResultParams 传递标题和 URL 给 WebPage,实现从原生到 Web 的无缝跳转。
25.2 WebPage 网页嵌入
核心实现
WebPage 使用 ArkUI 的 Web 组件嵌入网页,配合 webview.WebviewController 控制页面行为:
typescript
// WebPage.ets
import { webview } from '@kit.ArkWeb';
@Component
struct WebPage {
controller: webview.WebviewController = new webview.WebviewController();
@State url: string = ''
@State title: string = ''
@State isLoading: boolean = true
build() {
NavDestination() {
Column() {
TopBar({
title: this.title,
onBack: () => { this.pageContext.popPage(true) }
})
Stack() {
Web({ src: this.url, controller: this.controller })
.layoutWeight(1)
.onPageEnd(() => {
this.isLoading = false
})
LoadingProgress().width(40).height(40)
.visibility(this.isLoading ? Visibility.Visible : Visibility.None)
}.layoutWeight(1)
}
}
.onReady((ctx: NavDestinationContext) => {
const params = ctx.pathInfo.param as ResultParams;
this.url = params.url as string
this.title = params.title as string
})
}
}
ResultParams 接口
typescript
export interface ResultParams {
score?: number
firstScore?: number
secondScore?: number
thirdScore?: number
forthScore?: number
oddScore?: number
evenScore?: number
type?: number
title?: string
url?: string
}
该接口同时服务多个页面(积分详情、Web 页面等),通过 title 和 url 字段驱动 WebPage 的标题栏和网页地址。
加载状态处理
onPageEnd 回调在网页加载完成时触发,关闭 LoadingProgress 指示器:
typescript
.onPageEnd(() => {
this.isLoading = false
})
isLoading 默认为 true,页面初始即显示加载动画,加载完成后切换为 Web 内容。
25.3 SettingPage 设置页
SettingPage 提供隐私政策入口与退出登录功能:
typescript
// SettingPage.ets
@Component
struct SettingPage {
build() {
NavDestination() {
Column() {
TopBar({ title: '设置', onBack: () => { this.pageContext.popPage(true) } })
Column({ space: 12 }) {
CardItem({ name: '儿童隐私政策' }).onClick(() => {
this.pageContext.openPage({
param: {
title: '儿童隐私政策',
url: 'http://www.ai-youtoo.cn/partner_children_privacy.html',
} as ResultParams,
routerName: 'WebPage',
}, true);
})
CardItem({ name: '关于我们' }).onClick(() => {
this.pageContext.openPage({ routerName: 'AboutPage' }, true);
})
}
.backgroundColor($r('app.color.color_card'))
.borderRadius(12).padding(12)
Blank().layoutWeight(1)
Button('退出登录', { type: ButtonType.Normal })
.backgroundColor($r('app.color.app_alert'))
.onClick(async () => {
UserInfoManager.clearUserInfo()
this.pageContext.popPage(true, new Object({ index: 0 }))
});
}
}
}
}
退出登录时调用 UserInfoManager.clearUserInfo() 清除本地用户数据,然后返回上一页。MineView 通过 onReturn 回调自动刷新登录状态。
25.4 AboutPage 关于页
AboutPage 展示应用品牌信息:
typescript
// AboutPage.ets
@Component
struct AboutPage {
build() {
NavDestination() {
Column({ space: 10 }) {
TopBar({ title: '关于我们', onBack: () => { this.pageContext.popPage(true) } })
Column({ space: 10 }) {
Image($r('app.media.startIcon')).width(60).height(60).margin({ top: 20 })
Text('柚兔学伴')
.fontSize(18)
.margin({ top: 10, bottom: 40 })
Blank().layoutWeight(1)
Text('柚兔(山东)网络科技有限责任公司')
.fontSize(12).fontColor($r('app.color.color_text')).margin({ top: 30 })
Text('鲁ICP备2024092126号-4A')
.fontSize(12).fontColor($r('app.color.color_text'))
}
}
}
}
}
布局采用 Blank().layoutWeight(1) 将公司信息推至底部,营造居中对称的视觉感。
25.5 项目架构总览
九模块架构
StudyPartner/
├── entry/ # 主入口模块(EntryAbility、HomePage)
├── common/ # 公共模块(工具类、管理器、通用组件)
├── feature/
│ ├── todo/ # 待办与诗词模块
│ ├── stroke/ # 笔画查询模块
│ └── my/ # 个人中心模块
├── backup_air/ # 云备份与登录模块
├── network/ # 网络请求模块
└── smartxplayer/ # 音频播放模块
模块化优势:
- 功能边界清晰,团队可并行开发
- 模块可独立编译测试,提升构建效率
- 公共能力统一收敛到 common,避免代码重复
MVVM 模式
View (Component) ←→ ViewModel (Model类) ←→ Model (Database/Preferences)
以 StrokeView 为例:
- View:StrokeView 组件,负责 UI 渲染与用户交互
- ViewModel:StrokeModel 单例,管理笔画数据与 AI 对话状态
- Model:CharacterService + 云端 API,提供汉字数据
25.6 最佳实践总结
通用组件抽取
项目抽取了高频使用的通用组件:
| 组件 | 用途 | 使用位置 |
|---|---|---|
| TopBar | 顶部导航栏 | 所有二级页面 |
| CardItem | 列表行项 | MineView、SettingPage |
| IconText | 图标+文字功能入口 | TodoView |
| LoadingView | 全局加载态 | TodoView |
| CustomImageToggle | 自定义开关 | TodoView 任务完成 |
抽取原则: 同一 UI 模式出现 3 次以上即抽取为通用组件。
数据库封装模式
所有 RDB 数据库遵循统一封装范式:
typescript
class XxxDatabase {
private store: relationalStore.RdbStore | null = null;
async initialize() { /* getRdbStore + executeSql */ }
async addXxx() { /* insert */ }
async getXxxById() { /* query + equalTo */ }
async getAllXxx() { /* query + orderByDesc */ }
async updateXxx() { /* update + equalTo */ }
async deleteXxx() { /* delete + equalTo */ }
async close() { /* store.close */ }
private resultSetToXxx() { /* ResultSet → 接口对象 */ }
}
TodoDatabase、GiftExchangeDatabase 均遵循此模式,新增数据实体只需替换表名和字段。
单例模式
需要全局共享状态的管理器使用单例:
typescript
BackupManager.getInstance()
UserInfoManager (静态方法)
RecordUtils.getInstance()
BreakpointSystem
HttpManager
控制器模式
组件间通信采用控制器解耦:
TimerComponentController:外部控制倒计时启动/暂停/重置LottieController:控制 Lottie 动画播放/暂停/停止webview.WebviewController:控制 Web 页面前进/后退/刷新
全局状态管理
typescript
// 写入
AppStorage.setOrCreate('GlobalInfoModel', model)
// 只读绑定
@StorageProp('GlobalInfoModel') globalInfoModel: GlobalInfoModel = AppStorage.get('GlobalInfoModel')!
@StorageProp 单向绑定,UI 只读不写;@StorageLink 双向绑定,UI 可修改。
25.7 性能优化要点
LazyForEach 懒加载
长列表使用 LazyForEach 替代 ForEach,按需加载列表项:
typescript
List() {
LazyForEach(this.dataSource, (item) => {
ListItem() { /* ... */ }
})
}
ForEach 一次性创建所有节点,LazyForEach 仅渲染可视区域,大幅减少内存占用。
图片缓存与资源管理
- 图片资源使用
$r()引用,系统自动管理缓存 - Lottie 动画在
aboutToDisappear中清理控制器 - Canvas 上下文在页面销毁时置空
定时器清理
所有 setInterval 必须在 aboutToDisappear 中清除:
typescript
aboutToDisappear(): void {
if (this.internalId) {
clearInterval(this.internalId)
}
if (this.strokeManager != null) {
this.strokeManager.stopAnimation();
this.strokeManager.resetAnimation()
}
if (this.context) {
this.context.clearRect(0, 0, 100, 100);
this.context = null
}
}
25.8 发布准备
签名配置
Release 版本需要配置正式签名:
json5
// build-profile.json5
{
"signingConfigs": [{
"name": "release",
"type": "HarmonyOS",
"material": {
"certpath": "certs/release.cer",
"storePassword": "...",
"keyAlias": "release",
"keyPassword": "...",
"profile": "certs/release.p7b",
"signAlg": "SHA256withECDSA",
"storeFile": "certs/release.p12"
}
}]
}
版本管理
versionCode:递增整数,每次发布 +1versionName:语义化版本号(如 1.0.0)
严格模式合规
ArkTS 严格模式下需注意:
- 禁止使用
any类型 - 禁止运行时类型变更
- 必须显式初始化类属性
- 空安全:可选属性需用
?.或非空断言!!
全系列总结
至此,「柚兔学伴」HarmonyOS 项目教程全 25 讲完结。我们从项目创建与基础导航开始,逐步覆盖了:
- 基础架构:多模块工程、Navigation 导航、TabBar 布局
- 数据持久化:Preferences 轻量存储、RDB 关系数据库
- 核心功能:待办管理、诗词朗读、笔画查询、字帖生成、PDF 导出
- 进阶能力:AI 智能体对话、Lottie 动画、Canvas 绘制、语音交互
- 生态集成:华为账号登录、云存储、Web 嵌入、系统文件选择器
- 工程实践:组件抽取、单例模式、控制器解耦、性能优化、发布流程
HarmonyOS 的 ArkUI 声明式开发范式为儿童教育应用提供了丰富而高效的能力支撑。希望本系列能帮助开发者在鸿蒙生态中快速构建高质量应用。