欢迎光临
我们一直在努力

第01篇|源码导览:从 EntryAbility 到 Index.ets,看首页、路由、登录与启动闭环

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

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

在这里插入图片描述

系列导航

  • 第 2 篇:时间地点页源代码拆解
  • 第 3 篇:相机页源代码拆解
  • 第 4 篇:工作记录页源码导览
  • 第 5 篇:工程底座源码导览

摘要

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

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

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

目录

  • 整体结构
  • 启动阶段底座
  • Index.ets 分发中心
  • 底部导航与子页返回
  • NavPathStack 深层跳转
  • 首页职责拆解
  • 配图建议
  • 实操检查清单
  • 参考资料
  • 一、先看整体结构

    在这里插入图片描述

    渲染错误: 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 状态,而是可以恢复的业务状态。

    它会按顺序找三层信息:

  • AppStorage 里的当前 openId
  • session.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 处可直接定位到源码文件的引用
    • 是否有目录、总结、参考资料
    • 是否至少讲清楚一个完整业务闭环
    赞(0)
    未经允许不得转载:171主机测评 » 第01篇|源码导览:从 EntryAbility 到 Index.ets,看首页、路由、登录与启动闭环
    分享到: 更多 (0)

    评论 抢沙发

    • 昵称 (必填)
    • 邮箱 (必填)
    • 网址