鸿蒙应用架构设计:基于 ArkTS 的声明式 UI 与响应式状态管理
基于鸿蒙 ArkTS 的应用架构设计,涵盖声明式 UI 与响应式状态管理。内容包括开屏页倒计时跳转、学习模块打卡与目标统计、用户登录数据持久化、首页分类切换与搜索功能,以及多级分类筛选机制。通过组件化设计与状态驱动渲染,实现了前后端解耦与流畅交互,支持多端部署。

基于鸿蒙 ArkTS 的应用架构设计,涵盖声明式 UI 与响应式状态管理。内容包括开屏页倒计时跳转、学习模块打卡与目标统计、用户登录数据持久化、首页分类切换与搜索功能,以及多级分类筛选机制。通过组件化设计与状态驱动渲染,实现了前后端解耦与流畅交互,支持多端部署。


开屏页(Advertising):展示广告图片,包含倒计时功能,倒计时结束后自动跳转至主页面,提供用户短暂的品牌展示和加载时间
首页(Index.ets):应用主框架,实现底部 Tab 导航首页、学习、消息、我的,通过状态管理控制当前显示页面,并负责整体布局和背景设置
学习页(Learn):整合学习工具打卡、目标、学习平台和面试大全等模块入口,以卡片形式展示各类学习功能,提供清晰的学习路径导航
消息页(Message):展示消息统计总数量/未读数量,分类显示系统消息和通知公告,支持下拉刷新,点击可查看详情,提供未读消息计数提示
我的页(Mine):显示用户个人信息头像、昵称,提供编辑个人信息入口,整合功能列表反馈、设置等,处理登录/未登录状态的差异化展示和退出登录逻辑
登录页(LoginPage):实现用户身份认证,支持账号密码输入验证,含用户协议和隐私政策勾选及查看功能

倒计时控制机制通过状态变量 countDownSeconds(默认 3 秒)实现,每秒递减一次。
当倒计时结束时,系统会清除定时器以防内存泄漏,从 PreferencesUtil 恢复用户 token 和信息,并通过 router.replaceUrl 跳转到首页,同时在 onPageHide 中清理相关资源
// 展示开屏页面时间,单位秒
@State countDownSeconds: number = CommonConstant.ADVERTISING_TIME;
// 结束时间,单位秒
endTime: number = CommonConstant.ADVERTISING_END_TIME;
/**
* 生命周期函数
*/
onPageShow() {
this.endTime = setInterval(async () => {
if (this.countDownSeconds === CommonConstant.ADVERTISING_END_TIME) {
// 清除定时器
clearInterval(this.endTime);
// 获取对应的 token 等数据
const token = PreferencesUtil.getData(CommonConstant.PREFERENCES_NAME, CommonConstant.TOKEN_NAME, '');
const userInfo = PreferencesUtil.getData(CommonConstant.PREFERENCES_NAME, CommonConstant.USER_INFO, '');
if (token && userInfo) {
AppStorage.setOrCreate(CommonConstant.TOKEN_NAME, token);
AppStorage.setOrCreate(CommonConstant.USER_INFO, JSON.parse(userInfo));
}
// 跳转到首页
router.replaceUrl({ url: RouterConstant.PAGE_INDEX });
} else {
this.countDownSeconds--;
}
}, 1000);
}
/**
* 生命周期函数:隐藏页面
*/
onPageHide() {
// 清除定时任务
clearInterval(this.endTime);
}


判断当前位置是否存在且当天未打卡。如果未打卡,则调用 learnClockApi.learnClock 提交打卡信息。显示成功提示后跳转到学习工具页。若当天已打卡,则提示用户无需重复打卡。
// 核心打卡功能实现
async clock() {
if (this.location !== '') {
if (!this.isClock) {
// 调用 API 进行打卡
await learnClockApi.learnClock({ location: this.location, content: '学习打卡' });
showToast('打卡成功,已连续打卡 200 天');
router.replaceUrl({ url: RouterConstant.VIEWS_LEARN_TOOL });
} else {
showToast('今日已打过卡,不需要重复打卡');
}
}
}

通过 targetInfoApi 获取用户目标进度和分页数据。根据总数判断是否还有更多内容。若 isFlushed 为真,则刷新数据,否则追加记录更新页面显示状态并重置加载标识,防止重复操作。若 isUpdate 为真,则弹出已更新提示。
async getUserTargetData(isFlushed: boolean, isUpdate: boolean) {
// 获取用户整体目标完成进度统计
this.countData = await targetInfoApi.getTargetInfoCount();
// 分页查询用户整体目标
const pageResult = await targetInfoApi.pageListTargetInfo({ page: this.page, pageSize: this.pageSize });
this.total = pageResult.total;
// 判断总数据
if (this.total > this.page * this.pageSize) {
this.textShow = false;
} else {
this.textShow = true;
}
// 判断是否是刷新还是下拉
isFlushed ? this.targetInfoVos = pageResult.records : this.targetInfoVos.push(...pageResult.records);
// 展示页面数据
this.isShow = true;
// 节流,防止用户重复下拉
this. = ;
. = ;
(isUpdate) {
();
}
}

通过 IBestDialog 弹出对话框添加新目标。验证输入后调用 targetInfoApi.addTargetInfo 保存并刷新列表。列表尾端每个目标提供'完成'和'删除'按钮。完成目标通过 targetInfoApi.completeTargetInfo 更新状态并提示。删除目标通过 IBestDialogUtil 弹出确认框后调用 targetInfoApi.deleteTargetInfo 删除。
// 添加目标对话框
IBestDialog({
visible: $dialogVisible,
title: "添加目标",
showCancelButton: true,
defaultBuilder: (): void => this.formInputContain(),
beforeClose: async (action) => {
if (action === 'cancel') { return true; }
const value = this.inputTargetValue.trim();
this.formInputError = !value;
if (this.formInputError) {
showToast('请输入目标内容');
return false; // 阻止关闭
}
// 添加新目标
await targetInfoApi.addTargetInfo({ content: value });
showToast('添加目标成功');
// 刷新列表
this.page = 1;
await this.aboutToAppear();
return true; // 允许关闭对话框
}
});
// 列表尾端操作
@Builder itemEnd(item: TargetInfoVo) {
() {
({ : . }) {
($r())
.()
.();
}
.( () => {
(item. === ) {
();
;
}
targetInfoApi.({ : item. });
();
router.({ : . });
}).({ : });
({ : . }) {
($r())
.()
.();
}
.({ : })
.( () => {
.({
: ,
: ,
: ,
: () => {
targetInfoApi.({ : item. });
();
router.({ : . });
}
});
});
}.(.);
}


用户登录成功后,将 token 和用户信息同时保存到内存和设备存储中,实现数据持久化。通过 AppStorage 存入内存,方便应用运行时快速访问。通过 PreferencesUtil 存入设备存储,保证应用重启后仍可读取。调用 userApi.getUserInfo 获取用户信息并同步保存。
// 登录成功后数据持久化存储
if (token) {
// 内存存储
AppStorage.setOrCreate(CommonConstant.TOKEN_NAME, token);
// 设备存储
PreferencesUtil.savaData(CommonConstant.PREFERENCES_NAME, CommonConstant.TOKEN_NAME, token);
// 获取并存储用户信息
const userInfo = await userApi.getUserInfo();
AppStorage.setOrCreate(CommonConstant.USER_INFO, userInfo);
PreferencesUtil.savaData(CommonConstant.PREFERENCES_NAME, CommonConstant.USER_INFO, JSON.stringify(userInfo));
}

通过两个 Column 分别显示'最新'和'最热门'标题。点击标题时切换下划线显示状态以标识选中项。重置分页并触发对应数据加载最新文章或热门文章。
Row({ space: 10 }) {
Column() {
Text($r('app.string.article_best_new'))
.textStyles();
// 选中标题会出现下划线
if (this.newDividerShow) {
Divider().dividerStyles();
}
}
.onClick(() => {
// 展示下划线
this.hotDividerShow = false;
this.newDividerShow = true;
// 查询最新文章数据
this.isShow = false;
this.page = 1;
this.getArticleNewDataList(true, true);
});
Column() {
Text($r('app.string.article_best_hot'))
.textStyles();
// 选中标题会出现下划线
if (this.hotDividerShow) {
Divider().dividerStyles();
}
}
.onClick(() => {
this.newDividerShow = false;
this.hotDividerShow = true;
// 查询最热门文章数据
. = ;
. = ;
});
}

通过 Search 组件输入关键字。设置占位符和字体样式。用户提交时将关键字编码后赋值给标题。重置分页并调用 getArticleDataList 查询匹配的文章列表。保证搜索结果与界面展示同步更新。
// 搜索
Search({ placeholder: '请输入关键字', value: $$this.keyword })
.placeholderFont({ size: 14 })
.textFont({ size: 14 })
.onSubmit(() => {
// 处理标题
this.title = encodeURIComponent(this.keyword);
// 分页查询文章内容
this.isShow = false;
this.page = 1;
this.getArticleDataList(true);
})
.width(CommonConstant.WIDTH_FULL)
.height(30);

通过 Refresh 包裹 List 渲染文章内容。每个列表项可点击跳转到文章详情页。滚动到底部时判断是否有更多数据,若有则分页加载最新或最热门文章。更新加载状态,防止重复请求。下拉刷新时重置分页和加载状态重新获取对应分类的文章数据,保证列表展示与数据同步更新。
Refresh({ refreshing: $$this.isRefreshing }) {
// 内容组件
List() {
ForEach(this.articleContentList, (item: ArticleContentData) => {
ListItem() {
ArticleComponent({ articleContentData: item })
.onClick(() => {
// 路由到内容详情页
router.pushUrl({ url: RouterConstant.VIEWS_HOME_ARTICLE_INFO, params: { "articleId": item.id } });
});
}
});
}
.onReachEnd(() => {
if (!this.isLoad) {
if (this.total > this.page * this.pageSize) {
this.isLoad = true;
this.page++;
if (this.newDividerShow) {
// 分页查询最新文章数据
this.getArticleNewDataList(false, false);
}
if (this.) {
.(, );
}
} {
. = ;
}
}
})
.( {
(!.) {
. = ;
. = ;
. = ;
(.) {
.(, );
}
(.) {
.(, );
}
}
});

通过状态变量记录当前选中分类(全部、基础、进阶、高级)。点击不同分类时切换下划线显示状态。重置分页。将选中的难度赋值给 difficultyCategory。调用 getArticleList 重新加载对应分类的文章列表。实现界面与数据同步更新的条件筛选效果。
// 文章难度分类相关状态
@State allDividerShow: boolean = true;
@State basicDividerShow: boolean = false;
@State advancedDividerShow: boolean = false;
@State seniorDividerShow: boolean = false;
// 当前难度分类值
@State difficultyCategory: string = '';
// 当前内容分类(从路由参数获取)
@State contentCategory: string = '';
// 条件筛选标题栏
Row({ space: 10 }) {
Column() {
Text($r('app.string.all_condition'))
.textStyles();
// 选中标题会出现下划线
if (this.allDividerShow) {
Divider().dividerStyles();
}
}
.onClick(() => {
// 更新选中状态
this.allDividerShow = true;
this.basicDividerShow = false;
this.advancedDividerShow = false;
this. = ;
. = ;
. = ;
.(, );
});
() {
($r())
.();
(.) {
().();
}
}
.( {
. = ;
. = ;
. = ;
. = ;
. = ;
. = ;
. = ;
.(, );
});
() {
($r())
.();
(.) {
().();
}
}
.( {
. = ;
. = ;
. = ;
. = ;
. = ;
. = ;
. = ;
.(, );
});
() {
($r())
.();
(.) {
().();
}
}
.( {
. = ;
. = ;
. = ;
. = ;
. = ;
. = ;
. = ;
.(, );
});
}
通过 '百得知识库' 项目充分体现鸿蒙应用在组件化设计、状态驱动渲染与分布式数据管理上的优势,借助 ArkTS 与方舟框架的声明式编程模型,结合 @State、AppStorage、Router 等特性,实现前后端解耦与流畅交互,依托分布式架构与组件复用能力,开发者可以在统一代码体系下快速构建多端协同场景,实现 '一次开发,多端部署'。

微信公众号「极客日志」,在微信中扫描左侧二维码关注。展示文案:极客日志 zeeklog
将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online
将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online
将 Markdown(GFM)转为 HTML 片段,浏览器内 marked 解析;与 HTML 转 Markdown 互为补充。 在线工具,Markdown 转 HTML在线工具,online
将 HTML 片段转为 GitHub Flavored Markdown,支持标题、列表、链接、代码块与表格等;浏览器内处理,可链接预填。 在线工具,HTML 转 Markdown在线工具,online
通过删除不必要的空白来缩小和压缩JSON。 在线工具,JSON 压缩在线工具,online
将JSON字符串修饰为友好的可读格式。 在线工具,JSON美化和格式化在线工具,online