第01篇|源码导览:从 EntryAbility 到 Index.ets,看首页、路由、登录与启动闭环
时间轨迹第 1 篇:从 EntryAbility 到 Index.ets,把首页、路由、登录与启动闭环讲清楚
这篇文章不只讲“首页长什么样”,而是把
时间轨迹的入口层讲透:应用怎么启动、怎么恢复登录、怎么决定显示哪个页面、为什么子页能稳定返回。
如果你只看一篇总览,先看这一篇。

系列导航
摘要
时间轨迹 的首页不是单纯的 UI 页面,而是整个应用的调度中心。EntryAbility 负责把运行时环境、持久化状态、全屏和系统栏先搭好;Index.ets 则负责把首页、相机页、模板页、我的页、时间地点页、工作记录页、登录页这些入口统一串起来。
这套设计的关键不是“页面多”,而是“状态不乱”:
- 启动后能够恢复登录
- 主 Tab 和子页面分层清晰
- 返回逻辑不会互相打架
- 深层功能页可以通过
NavPathStack单独跳转
目录
- 整体结构
- 启动阶段底座
Index.ets分发中心- 底部导航与子页返回
NavPathStack深层跳转- 首页职责拆解
- 配图建议
- 实操检查清单
- 参考资料
一、先看整体结构

这张图说明了一件事:Index.ets 不是“一个首页组件”,而是整个应用的状态中枢。
图 1 说明
这张图可以直接拿去放在 CSDN 里做首个流程图。它要表达的核心不是页面名,而是“入口层先启动,首页再分发,子页再承接”。
二、启动阶段先把底座立住
在 EntryAbility.ets 里,最重要的不是页面,而是应用级状态的初始化。
PersistentStorage.persistProp('privacyAgreed', true);
PersistentStorage.persistProp('isHuaweiLoggedIn', false);
PersistentStorage.persistProp('huaweiNickname', '');
PersistentStorage.persistProp('huaweiOpenID', '');
PersistentStorage.persistProp('userAvatarUri', '');
PersistentStorage.persistProp('userDataDir', '');
PersistentStorage.persistProp('wmCustomText', '');
PersistentStorage.persistProp('cameraWatermarkTemplate', 0);
PersistentStorage.persistProp('wmAutoWatermark', true);
PersistentStorage.persistProp('wmShowTime', true);
PersistentStorage.persistProp('wmShowAddress', true);
PersistentStorage.persistProp('wmLocationHistory', '[]');
PersistentStorage.persistProp('wrAutoRecord', true);
PersistentStorage.persistProp('wrCurrentType', '会议记录');
PersistentStorage.persistProp('wrRecords', '[]');
PersistentStorage.persistProp('cameraSaveMode', 'local');
PersistentStorage.persistProp('cameraPhotoRatio', '4:3');
这段初始化的意义很直接:
- 让首页、相机页、时间地点页、工作记录页共享统一状态
- 让用户重启应用后不会把配置丢掉
- 让后续页面只需要读状态,不需要反复猜默认值
紧接着,EntryAbility 还会设置全屏和安全区:
win.setWindowLayoutFullScreen(true);
win.setWindowSystemBarProperties({
statusBarColor: '#00000000',
navigationBarColor: '#00000000'
});
const topArea: window.AvoidArea = win.getWindowAvoidArea(window.AvoidAreaType.TYPE_SYSTEM);
const bottomArea: window.AvoidArea = win.getWindowAvoidArea(window.AvoidAreaType.TYPE_NAVIGATION_INDICATOR);
AppStorage.setOrCreate<number>('statusBarHeight', px2vp(topArea.topRect.height) || 44);
AppStorage.setOrCreate<number>('navBarHeight', px2vp(bottomArea.bottomRect.height) || 0);
这一步很重要,因为后面很多页面都要根据状态栏和导航栏的高度做布局校正。否则看起来像“页面没问题”,实际是被系统区域挡住了。
三、Index.ets 是真正的分发中心
Index.ets 里最核心的状态非常少,但职责很重:
const MAIN_TABS: string[] = ['home', 'camera', 'templates', 'profile'];
@Provide('navPathStack') navPathStack: NavPathStack = new NavPathStack();
@State selectedTab: string = 'home';
@State subPageReturnTab: string = 'profile';
@State loginReturnTab: string = 'profile';
这里的设计思路很清晰:
selectedTab决定当前显示哪个页面subPageReturnTab决定子页面返回到哪里loginReturnTab决定登录页完成后回到哪里navPathStack专门负责更深层的独立路由跳转
1. 首页和子页不是同一层
private isSubPage(): boolean {
return !MAIN_TABS.includes(this.selectedTab);
}
这个判断看起来简单,但它把应用分成了两个层次:
- 主 Tab:
home / camera / templates / profile - 子页面:
timeLocation / workRecord / settings / photoAlbum / videoAlbum / loginPage
分层后,底部导航栏显示逻辑就不会乱。
2. 启动时先尝试恢复登录
if (!this.isLoggedIn) {
const ctx = getContext(this);
let openId = this.huaweiOpenID;
let nickname = this.huaweiNickname;
if (!openId) {
openId = AccountService.getSessionOpenId(ctx);
nickname = AccountService.getSessionNickname(ctx);
}
if (openId !== '') {
AccountService.loadUserData(ctx, openId);
this.huaweiOpenID = openId;
this.huaweiNickname = nickname;
this.userDataDir = ctx.filesDir + '/accounts/' + openId;
this.isLoggedIn = true;
}
}
这段代码的价值在于:登录态不是临时 UI 状态,而是可以恢复的业务状态。
它会按顺序找三层信息:
AppStorage里的当前 openIdsession.json里的会话信息- 本地账号目录里的业务数据
所以用户不是每次打开都要重新走一遍登录流程。
3. 首页负责“页面分发”
Index.ets 通过 selectedTab 把不同功能页切出去:
if (this.selectedTab === 'home') {
HomePage({
onGoCamera: () => { this.selectedTab = 'camera'; },
onGoTemplates: () => { this.fromCamera = false; this.selectedTab = 'templates'; },
onGoAI: () => { this.navPathStack.pushPathByName('AIToolsPage', null); },
onGoPhotoAlbum: () => { this.albumReturnTab = 'home'; this.initialViewPhoto = ''; this.selectedTab = 'photoAlbum'; },
onGoTimeLocation: () => { this.subPageReturnTab = 'home'; this.selectedTab = 'timeLocation'; },
onGoWorkRecord: () => { this.subPageReturnTab = 'home'; this.selectedTab = 'workRecord'; }
});
}
这种写法的好处是:
- 页面跳转逻辑集中,容易维护
- 首页是总入口,但不会把所有功能硬塞到一个组件里
- 深层功能可以独立扩展,而不用改坏主 Tab
1. 关键文件地图
| 文件 | 作用 |
|---|---|
entry/src/main/ets/entryability/EntryAbility.ets |
初始化持久化、窗口、主题、后台保存 |
entry/src/main/ets/pages/Index.ets |
负责全局页面分发与 Tab 调度 |
entry/src/main/ets/utils/AccountService.ets |
负责会话和业务数据的读写 |
entry/src/main/resources/base/profile/main_pages.json |
定义真正的应用入口页 |
entry/src/main/resources/base/profile/route_map.json |
定义独立路由页 |
2. 首页为什么要集中处理返回逻辑
如果返回逻辑分散在每个子页里,就会出现两个问题:
- 子页返回后可能落到错误的主 Tab
- 未来新增页面时,需要改很多处
onBack
Index.ets 把这些返回目标统一维护,新增页面时只要补一个入口和一个返回目标,成本就比较可控。
四、为什么首页底部导航不会乱
底部导航并不是“每个页面都显示”,而是由当前页面层级决定。
Index.ets 里会根据 selectedTab 和 isSubPage() 决定导航栏位置和显示方式。这样做有两个效果:
- 主 Tab 页面保留底部导航,方便切换
- 子页面进入后隐藏或弱化底部导航,避免双重导航冲突
对于移动端应用来说,这种层级感很重要。用户知道自己在“主入口”还是“子功能”里,不会迷路。
五、NavPathStack 负责更深一层的跳转
不是所有页面都适合塞进 selectedTab。像 AIToolsPage、WatermarkEditPage 这种更像独立功能栈的页面,项目里用的是 NavPathStack。
onGoAI: () => { this.navPathStack.pushPathByName('AIToolsPage', null); }
这说明项目里有两种跳转模型:
selectedTab:适合主入口、轻量子页、状态切换NavPathStack:适合更深层、更独立的功能页
这也是这个项目结构比较成熟的地方。边界清楚,后面继续加页面时不容易失控。
六、首页到底承担了哪些职责
把 Index.ets 的职责压缩成一句话:
它不是首页,而是应用状态与页面流转的控制台。
它具体负责:
- 主 Tab 和子页之间的切换
- 启动时自动恢复登录
- 接收来自首页卡片和功能入口的动作
- 维护登录页、相册页、工作记录页的返回目标
- 协调
NavPathStack的独立路由
七、配图建议
建议这篇文章至少放 2 张图:
- 封面图:
D:/APP/APP_22/article_media/cover.png - 首页效果图:
D:/APP/APP_22/article_media/home.png
如果要再补一张结构图,可以把启动闭环放成流程图,和正文里的 Mermaid 对应起来。
八、实操检查清单
- 启动后首页是否能正常恢复登录态
- 主 Tab 切换是否稳定
- 进入子页后返回是否回到正确的入口
NavPathStack页面是否不会污染主 Tab 状态- 重启应用后
PersistentStorage和AppStorage是否仍然可用
结语
这一篇真正要解决的问题,不是“首页怎么写”,而是“整个应用怎么有秩序地启动和切页”。
当 EntryAbility 把底座搭稳,Index.ets 把分发逻辑收拢,后面的水印页、相机页、工作记录页才能真正跑成一条连续链路,而不是一堆互相独立的页面。
小结表
| 问题 | 这篇文章给出的答案 |
|---|---|
| 应用从哪开始 | EntryAbility 先初始化底座 |
| 首页怎么切页 | Index.ets 统一分发 |
| 子页怎么返回 | subPageReturnTab / loginReturnTab |
| 深层页怎么跳 | NavPathStack |
| 为什么重启能恢复 | PersistentStorage + AccountService |
问题 - 方案 - 验证
问题
首页、子页、登录页、相册页如果各自维护返回逻辑,页面之间会互相干扰。
方案
把返回目标统一放在 Index.ets,由它来决定各个页面的入口和回退点。
验证
实际切换 timeLocation、workRecord、loginPage 后,返回目标都能回到预期主入口,不会跳错 Tab。
常见问题
1. 为什么主页面和子页面要分层
因为 selectedTab 负责的是“当前看见哪个页面”,而 NavPathStack 负责的是“更深层的独立跳转”。如果把两者混在一起,底部导航和返回行为就会变复杂。
2. 为什么登录恢复要放在 Index.ets
因为首页是整个应用的入口,只有这里能统一协调启动恢复、底部导航和账号状态。如果把恢复逻辑散到各个页面,重启后就很难保持一致。
3. 为什么子页要通过 subPageReturnTab 回去
因为子页不应该硬编码回到首页。它要根据上一个主入口回退,才能保持用户上下文。
发布前检查
- 封面图是否是可见的 PNG
- 首页效果图是否在正文首屏附近
- 是否存在至少 3 处可直接定位到源码文件的引用
- 是否有目录、总结、参考资料
- 是否至少讲清楚一个完整业务闭环
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐



所有评论(0)