时间轨迹第 1 篇:从 EntryAbilityIndex.ets,把首页、路由、登录与启动闭环讲清楚

这篇文章不只讲“首页长什么样”,而是把 时间轨迹 的入口层讲透:应用怎么启动、怎么恢复登录、怎么决定显示哪个页面、为什么子页能稳定返回。
如果你只看一篇总览,先看这一篇。

在这里插入图片描述

系列导航

摘要

时间轨迹 的首页不是单纯的 UI 页面,而是整个应用的调度中心。EntryAbility 负责把运行时环境、持久化状态、全屏和系统栏先搭好;Index.ets 则负责把首页、相机页、模板页、我的页、时间地点页、工作记录页、登录页这些入口统一串起来。

这套设计的关键不是“页面多”,而是“状态不乱”:

  • 启动后能够恢复登录
  • 主 Tab 和子页面分层清晰
  • 返回逻辑不会互相打架
  • 深层功能页可以通过 NavPathStack 单独跳转

目录

  1. 整体结构
  2. 启动阶段底座
  3. Index.ets 分发中心
  4. 底部导航与子页返回
  5. NavPathStack 深层跳转
  6. 首页职责拆解
  7. 配图建议
  8. 实操检查清单
  9. 参考资料

一、先看整体结构

在这里插入图片描述

渲染错误: Mermaid 渲染失败: Parse error on line 4: ... C --> D[loadContent(Index)] D --> E -----------------------^ Expecting 'SQE', 'DOUBLECIRCLEEND', 'PE', '-)', 'STADIUMEND', 'SUBROUTINEEND', 'PIPE', 'CYLINDEREND', 'DIAMOND_STOP', 'TAGEND', 'TRAPEND', 'INVTRAPEND', 'UNICODE_TEXT', 'TEXT', 'TAGSTART', got 'PS'

这张图说明了一件事: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 状态,而是可以恢复的业务状态。

它会按顺序找三层信息:

  1. AppStorage 里的当前 openId
  2. session.json 里的会话信息
  3. 本地账号目录里的业务数据

所以用户不是每次打开都要重新走一遍登录流程。

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 里会根据 selectedTabisSubPage() 决定导航栏位置和显示方式。这样做有两个效果:

  • 主 Tab 页面保留底部导航,方便切换
  • 子页面进入后隐藏或弱化底部导航,避免双重导航冲突

对于移动端应用来说,这种层级感很重要。用户知道自己在“主入口”还是“子功能”里,不会迷路。

五、NavPathStack 负责更深一层的跳转

不是所有页面都适合塞进 selectedTab。像 AIToolsPageWatermarkEditPage 这种更像独立功能栈的页面,项目里用的是 NavPathStack

onGoAI: () => { this.navPathStack.pushPathByName('AIToolsPage', null); }

这说明项目里有两种跳转模型:

  • selectedTab:适合主入口、轻量子页、状态切换
  • NavPathStack:适合更深层、更独立的功能页

这也是这个项目结构比较成熟的地方。边界清楚,后面继续加页面时不容易失控。

六、首页到底承担了哪些职责

Index.ets 的职责压缩成一句话:

它不是首页,而是应用状态与页面流转的控制台。

它具体负责:

  • 主 Tab 和子页之间的切换
  • 启动时自动恢复登录
  • 接收来自首页卡片和功能入口的动作
  • 维护登录页、相册页、工作记录页的返回目标
  • 协调 NavPathStack 的独立路由

七、配图建议

建议这篇文章至少放 2 张图:

  1. 封面图:D:/APP/APP_22/article_media/cover.png
  2. 首页效果图:D:/APP/APP_22/article_media/home.png

如果要再补一张结构图,可以把启动闭环放成流程图,和正文里的 Mermaid 对应起来。

八、实操检查清单

  • 启动后首页是否能正常恢复登录态
  • 主 Tab 切换是否稳定
  • 进入子页后返回是否回到正确的入口
  • NavPathStack 页面是否不会污染主 Tab 状态
  • 重启应用后 PersistentStorageAppStorage 是否仍然可用

结语

这一篇真正要解决的问题,不是“首页怎么写”,而是“整个应用怎么有秩序地启动和切页”。

EntryAbility 把底座搭稳,Index.ets 把分发逻辑收拢,后面的水印页、相机页、工作记录页才能真正跑成一条连续链路,而不是一堆互相独立的页面。

小结表

问题 这篇文章给出的答案
应用从哪开始 EntryAbility 先初始化底座
首页怎么切页 Index.ets 统一分发
子页怎么返回 subPageReturnTab / loginReturnTab
深层页怎么跳 NavPathStack
为什么重启能恢复 PersistentStorage + AccountService

问题 - 方案 - 验证

问题

首页、子页、登录页、相册页如果各自维护返回逻辑,页面之间会互相干扰。

方案

把返回目标统一放在 Index.ets,由它来决定各个页面的入口和回退点。

验证

实际切换 timeLocationworkRecordloginPage 后,返回目标都能回到预期主入口,不会跳错 Tab。

常见问题

1. 为什么主页面和子页面要分层

因为 selectedTab 负责的是“当前看见哪个页面”,而 NavPathStack 负责的是“更深层的独立跳转”。如果把两者混在一起,底部导航和返回行为就会变复杂。

2. 为什么登录恢复要放在 Index.ets

因为首页是整个应用的入口,只有这里能统一协调启动恢复、底部导航和账号状态。如果把恢复逻辑散到各个页面,重启后就很难保持一致。

3. 为什么子页要通过 subPageReturnTab 回去

因为子页不应该硬编码回到首页。它要根据上一个主入口回退,才能保持用户上下文。

发布前检查

  • 封面图是否是可见的 PNG
  • 首页效果图是否在正文首屏附近
  • 是否存在至少 3 处可直接定位到源码文件的引用
  • 是否有目录、总结、参考资料
  • 是否至少讲清楚一个完整业务闭环
Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐