最佳实践 - 鸿蒙应用架构设计进阶:基于「百得知识库」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.isLoad = false
this.isRefreshing = false
// 是否刷新
if (isUpdate) {
showToast("已更新")
}
}
目标添加滑动功能

通过 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) {
Row() {
// 完成目标按钮
Button({ type: ButtonType.Circle }) {
Image($r('app.media.icon_finish'))
.width(40)
.aspectRatio(1)
}
.onClick(async () => {
if (item.status === '1') {
showToast('当前目标已完成,无需重复点击');
return;
}
await targetInfoApi.completeTargetInfo({ id: item.id });
showToast('目标已完成');
router.replaceUrl({ url: RouterConstant.VIEWS_LEARN_TARGET });
}).margin({ right: 10 })
// 删除目标按钮
Button({ type: ButtonType.Circle }) {
Image($r('app.media.icon_delete'))
.width(40)
.aspectRatio(1)
}
.margin({ right: 10 })
.onClick(async () => {
IBestDialogUtil.open({
title: "提示",
message: "是否确认删除当前目标?",
showCancelButton: true,
onConfirm: async () => {
await targetInfoApi.deleteTargetInfo({ id: item.id });
showToast('删除目标成功');
router.replaceUrl({ url: RouterConstant.VIEWS_LEARN_TARGET });
}
})
})
}.justifyContent(FlexAlign.SpaceBetween)
}
用户登录与隐私设置


用户登录成功后,将 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
// 查询最热门文章数据
this.isShow = false
this.page = 1
})
}
搜索功能实现

通过 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.hotDividerShow) {
// 分页查询最热门文章数据
this.getArticleHotDataList(false, false)
}
} else {
this.textShow = true
}
}
})
}
.onRefreshing(() => {
if (!this.isLoad) {
this.isLoad = true
this.textShow = false
// 页面恢复到1
this.page = 1
if (this.newDividerShow) {
// 分页查询最新文章数据
this.getArticleNewDataList(true, true)
}
if (this.hotDividerShow) {
// 分页查询最热门文章数据
this.getArticleHotDataList(true, true)
}
}
})
多级分类与筛选机制
状态设计与管理分类UI交互设计

通过状态变量记录当前选中分类(全部、基础、进阶、高级)
点击不同分类时切换下划线显示状态
重置分页
将选中的难度赋值给 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.seniorDividerShow = false
// 重置分页并重新加载数据
this.isShow = false
this.page = 1
this.getArticleList(true, true)
})
Column() {
Text($r('app.string.basic_condition'))
.textStyles()
if (this.basicDividerShow) {
Divider().dividerStyles()
}
}
.onClick(() => {
this.allDividerShow = false
this.basicDividerShow = true
this.advancedDividerShow = false
this.seniorDividerShow = false
this.isShow = false
this.page = 1
this.difficultyCategory = 'basic'
this.getArticleList(true, true)
})
Column() {
Text($r('app.string.advanced_condition'))
.textStyles()
if (this.advancedDividerShow) {
Divider().dividerStyles()
}
}
.onClick(() => {
this.allDividerShow = false
this.basicDividerShow = false
this.advancedDividerShow = true
this.seniorDividerShow = false
this.isShow = false
this.page = 1
this.difficultyCategory = 'advanced'
this.getArticleList(true, true)
})
Column() {
Text($r('app.string.senior_condition'))
.textStyles()
if (this.seniorDividerShow) {
Divider().dividerStyles()
}
}
.onClick(() => {
this.allDividerShow = false
this.basicDividerShow = false
this.advancedDividerShow = false
this.seniorDividerShow = true
this.isShow = false
this.page = 1
this.difficultyCategory = 'senior'
this.getArticleList(true, true)
})
}
总结

通过 "百得知识库" 项目充分体现鸿蒙应用在组件化设计、状态驱动渲染与分布式数据管理上的优势,借助 ArkTS 与方舟框架的声明式编程模型,结合 @State、AppStorage、Router 等特性,实现前后端解耦与流畅交互,依托分布式架构与组件复用能力,开发者可以在统一代码体系下快速构建多端协同场景,实现 "一次开发,多端部署"。
👉如果你也在探索鸿蒙轻量化应用开发,或是想第一时间 get 鸿蒙新特性适配,点击链接加入和开发者们一起交流经验吧!https://work.weixin.qq.com/gm/afdd8c7246e72c0e94abdbd21bc9c5c1
更多推荐
所有评论(0)