时间轨迹第 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});consttopArea:window.AvoidAreawin.getWindowAvoidArea(window.AvoidAreaType.TYPE_SYSTEM);constbottomArea:window.AvoidAreawin.getWindowAvoidArea(window.AvoidAreaType.TYPE_NAVIGATION_INDICATOR);AppStorage.setOrCreatenumber(statusBarHeight,px2vp(topArea.topRect.height)||44);AppStorage.setOrCreatenumber(navBarHeight,px2vp(bottomArea.bottomRect.height)||0);这一步很重要因为后面很多页面都要根据状态栏和导航栏的高度做布局校正。否则看起来像“页面没问题”实际是被系统区域挡住了。三、Index.ets是真正的分发中心Index.ets里最核心的状态非常少但职责很重constMAIN_TABS:string[][home,camera,templates,profile];Provide(navPathStack)navPathStack:NavPathStacknewNavPathStack();StateselectedTab:stringhome;StatesubPageReturnTab:stringprofile;StateloginReturnTab:stringprofile;这里的设计思路很清晰selectedTab决定当前显示哪个页面subPageReturnTab决定子页面返回到哪里loginReturnTab决定登录页完成后回到哪里navPathStack专门负责更深层的独立路由跳转1. 首页和子页不是同一层privateisSubPage():boolean{return!MAIN_TABS.includes(this.selectedTab);}这个判断看起来简单但它把应用分成了两个层次主 Tabhome / camera / templates / profile子页面timeLocation / workRecord / settings / photoAlbum / videoAlbum / loginPage分层后底部导航栏显示逻辑就不会乱。2. 启动时先尝试恢复登录if(!this.isLoggedIn){constctxgetContext(this);letopenIdthis.huaweiOpenID;letnicknamethis.huaweiNickname;if(!openId){openIdAccountService.getSessionOpenId(ctx);nicknameAccountService.getSessionNickname(ctx);}if(openId!){AccountService.loadUserData(ctx,openId);this.huaweiOpenIDopenId;this.huaweiNicknamenickname;this.userDataDirctx.filesDir/accounts/openId;this.isLoggedIntrue;}}这段代码的价值在于登录态不是临时 UI 状态而是可以恢复的业务状态。它会按顺序找三层信息AppStorage里的当前 openIdsession.json里的会话信息本地账号目录里的业务数据所以用户不是每次打开都要重新走一遍登录流程。3. 首页负责“页面分发”Index.ets通过selectedTab把不同功能页切出去if(this.selectedTabhome){HomePage({onGoCamera:(){this.selectedTabcamera;},onGoTemplates:(){this.fromCamerafalse;this.selectedTabtemplates;},onGoAI:(){this.navPathStack.pushPathByName(AIToolsPage,null);},onGoPhotoAlbum:(){this.albumReturnTabhome;this.initialViewPhoto;this.selectedTabphotoAlbum;},onGoTimeLocation:(){this.subPageReturnTabhome;this.selectedTabtimeLocation;},onGoWorkRecord:(){this.subPageReturnTabhome;this.selectedTabworkRecord;}});}这种写法的好处是页面跳转逻辑集中容易维护首页是总入口但不会把所有功能硬塞到一个组件里深层功能可以独立扩展而不用改坏主 Tab1. 关键文件地图文件作用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未来新增页面时需要改很多处onBackIndex.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为什么重启能恢复PersistentStorageAccountService问题 - 方案 - 验证问题首页、子页、登录页、相册页如果各自维护返回逻辑页面之间会互相干扰。方案把返回目标统一放在Index.ets由它来决定各个页面的入口和回退点。验证实际切换timeLocation、workRecord、loginPage后返回目标都能回到预期主入口不会跳错 Tab。常见问题1. 为什么主页面和子页面要分层因为selectedTab负责的是“当前看见哪个页面”而NavPathStack负责的是“更深层的独立跳转”。如果把两者混在一起底部导航和返回行为就会变复杂。2. 为什么登录恢复要放在Index.ets因为首页是整个应用的入口只有这里能统一协调启动恢复、底部导航和账号状态。如果把恢复逻辑散到各个页面重启后就很难保持一致。3. 为什么子页要通过subPageReturnTab回去因为子页不应该硬编码回到首页。它要根据上一个主入口回退才能保持用户上下文。发布前检查封面图是否是可见的 PNG首页效果图是否在正文首屏附近是否存在至少 3 处可直接定位到源码文件的引用是否有目录、总结、参考资料是否至少讲清楚一个完整业务闭环
第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});consttopArea:window.AvoidAreawin.getWindowAvoidArea(window.AvoidAreaType.TYPE_SYSTEM);constbottomArea:window.AvoidAreawin.getWindowAvoidArea(window.AvoidAreaType.TYPE_NAVIGATION_INDICATOR);AppStorage.setOrCreatenumber(statusBarHeight,px2vp(topArea.topRect.height)||44);AppStorage.setOrCreatenumber(navBarHeight,px2vp(bottomArea.bottomRect.height)||0);这一步很重要因为后面很多页面都要根据状态栏和导航栏的高度做布局校正。否则看起来像“页面没问题”实际是被系统区域挡住了。三、Index.ets是真正的分发中心Index.ets里最核心的状态非常少但职责很重constMAIN_TABS:string[][home,camera,templates,profile];Provide(navPathStack)navPathStack:NavPathStacknewNavPathStack();StateselectedTab:stringhome;StatesubPageReturnTab:stringprofile;StateloginReturnTab:stringprofile;这里的设计思路很清晰selectedTab决定当前显示哪个页面subPageReturnTab决定子页面返回到哪里loginReturnTab决定登录页完成后回到哪里navPathStack专门负责更深层的独立路由跳转1. 首页和子页不是同一层privateisSubPage():boolean{return!MAIN_TABS.includes(this.selectedTab);}这个判断看起来简单但它把应用分成了两个层次主 Tabhome / camera / templates / profile子页面timeLocation / workRecord / settings / photoAlbum / videoAlbum / loginPage分层后底部导航栏显示逻辑就不会乱。2. 启动时先尝试恢复登录if(!this.isLoggedIn){constctxgetContext(this);letopenIdthis.huaweiOpenID;letnicknamethis.huaweiNickname;if(!openId){openIdAccountService.getSessionOpenId(ctx);nicknameAccountService.getSessionNickname(ctx);}if(openId!){AccountService.loadUserData(ctx,openId);this.huaweiOpenIDopenId;this.huaweiNicknamenickname;this.userDataDirctx.filesDir/accounts/openId;this.isLoggedIntrue;}}这段代码的价值在于登录态不是临时 UI 状态而是可以恢复的业务状态。它会按顺序找三层信息AppStorage里的当前 openIdsession.json里的会话信息本地账号目录里的业务数据所以用户不是每次打开都要重新走一遍登录流程。3. 首页负责“页面分发”Index.ets通过selectedTab把不同功能页切出去if(this.selectedTabhome){HomePage({onGoCamera:(){this.selectedTabcamera;},onGoTemplates:(){this.fromCamerafalse;this.selectedTabtemplates;},onGoAI:(){this.navPathStack.pushPathByName(AIToolsPage,null);},onGoPhotoAlbum:(){this.albumReturnTabhome;this.initialViewPhoto;this.selectedTabphotoAlbum;},onGoTimeLocation:(){this.subPageReturnTabhome;this.selectedTabtimeLocation;},onGoWorkRecord:(){this.subPageReturnTabhome;this.selectedTabworkRecord;}});}这种写法的好处是页面跳转逻辑集中容易维护首页是总入口但不会把所有功能硬塞到一个组件里深层功能可以独立扩展而不用改坏主 Tab1. 关键文件地图文件作用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未来新增页面时需要改很多处onBackIndex.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为什么重启能恢复PersistentStorageAccountService问题 - 方案 - 验证问题首页、子页、登录页、相册页如果各自维护返回逻辑页面之间会互相干扰。方案把返回目标统一放在Index.ets由它来决定各个页面的入口和回退点。验证实际切换timeLocation、workRecord、loginPage后返回目标都能回到预期主入口不会跳错 Tab。常见问题1. 为什么主页面和子页面要分层因为selectedTab负责的是“当前看见哪个页面”而NavPathStack负责的是“更深层的独立跳转”。如果把两者混在一起底部导航和返回行为就会变复杂。2. 为什么登录恢复要放在Index.ets因为首页是整个应用的入口只有这里能统一协调启动恢复、底部导航和账号状态。如果把恢复逻辑散到各个页面重启后就很难保持一致。3. 为什么子页要通过subPageReturnTab回去因为子页不应该硬编码回到首页。它要根据上一个主入口回退才能保持用户上下文。发布前检查封面图是否是可见的 PNG首页效果图是否在正文首屏附近是否存在至少 3 处可直接定位到源码文件的引用是否有目录、总结、参考资料是否至少讲清楚一个完整业务闭环