《旅游住宿》四、HdsTabs 沉浸光感框架

HarmonyOS 分层架构 — phone 壳工程与 HdsTabs 沉浸光感框架实战

效果


一、phone 壳工程职责

在分层架构中,phone 模块是应用的入口壳工程(Entry Module),它不承载任何业务逻辑,而是承担以下四项职责:

职责说明
应用入口EntryAbility 作为 UIAbility 启动点,负责窗口初始化、安全区域采集
依赖聚合通过 oh-package.json5 汇聚 common + 所有 features/* 模块
Tab 导航MainPage 使用 HdsTabs 组装首页/游园/地图/我的四个 TabContent
路由注册与分发RouterConfig 注册全局路由表,routeMapBuilder 根据路由名分发页面

核心原则:phone 模块只做"胶水"——把各 feature 模块的页面"粘"到导航框架上,自身不包含业务 UI 组件。


二、工程配置

2.1 module.json5 — Entry 类型声明

{
  "module": {
    "name": "phone",
    "type": "entry",
    "description": "$string:module_desc",
    "mainElement": "EntryAbility",
    "deviceTypes": ["phone"],
    "deliveryWithInstall": true,
    "installationFree": false,
    "pages": "$profile:main_pages",
    "abilities": [
      {
        "name": "EntryAbility",
        "srcEntry": "./ets/entryability/EntryAbility.ets",
        "description": "$string:EntryAbility_desc",
        "icon": "$media:startIcon",
        "label": "$string:EntryAbility_label",
        "startWindowIcon": "$media:startIcon",
        "startWindowBackground": "$color:start_window_background",
        "exported": true,
        "skills": [
          {
            "entities": ["entity.system.home"],
            "actions": ["action.system.home"]
          }
        ]
      }
    ],
    "requestPermissions": [
      { "name": "ohos.permission.GET_NETWORK_INFO" },
      { "name": "ohos.permission.INTERNET" },
      {
        "name": "ohos.permission.APPROXIMATELY_LOCATION",
        "reason": "$string:module_desc",
        "usedScene": {
          "abilities": ["EntryAbility"],
          "when": "inuse"
        }
      },
      {
        "name": "ohos.permission.LOCATION",
        "reason": "$string:module_desc",
        "usedScene": {
          "abilities": ["EntryAbility"],
          "when": "inuse"
        }
      }
    ]
  }
}

关键配置解读

  • "type": "entry":标识为入口模块,每个 HAP 包有且仅有一个 entry。
  • "mainElement": "EntryAbility":指定 Ability 入口类,系统启动时自动调用其 onCreate
  • "pages": "$profile:main_pages":路由表指向 resources/base/profile/main_pages.json,注册所有可导航页面。
  • 权限声明:网络信息 + 互联网访问 + 模糊定位 + 精确定位,覆盖旅游 App 的地图与数据请求场景。

2.2 oh-package.json5 — 依赖聚合

{
  "name": "phone",
  "version": "1.0.0",
  "description": "手机端壳模块",
  "main": "",
  "author": "",
  "license": "",
  "dependencies": {
    "common": "file:../common",
    "home": "file:../features/home",
    "ParkService": "file:../features/ParkService",
    "MapService": "file:../features/MapService",
    "AccountCenter": "file:../features/AccountCenter",
    "PersonalCenter": "file:../features/PersonalCenter"
  }
}

架构意义

  • phone 模块是唯一同时依赖所有 feature 模块的层,它扮演"总装车间"角色。
  • 各 feature 之间互不依赖——ParkService 不知道 MapService 的存在,它们通过 common 层的 RouterService 间接通信。
  • "file:../" 路径引用方式适用于单仓库多模块(Monorepo)结构,hvigor 构建系统自动处理依赖解析。

2.3 build-profile.json5 — 构建配置

{
  "apiType": "stageMode",
  "buildOption": {
    "resOptions": {
      "copyCodeResource": {
        "enable": false
      }
    }
  },
  "buildOptionSet": [
    {
      "name": "release",
      "arkOptions": {
        "obfuscation": {
          "ruleOptions": {
            "enable": false,
            "files": ["./obfuscation-rules.txt"]
          }
        }
      }
    }
  ],
  "targets": [
    { "name": "default" },
    { "name": "ohosTest" }
  ]
}
  • "apiType": "stageMode":使用 Stage 模型(推荐),区别于旧版 FA 模型。
  • "copyCodeResource": { "enable": false }:不拷贝代码资源,减小包体积。
  • targetsdefault(正式构建)+ ohosTest(仪器测试),两套 target 共用同一份构建配置。

三、EntryAbility — 入口能力

EntryAbility 是整个 App 的生命周期起点,完整代码如下:

import { AbilityConstant, ConfigurationConstant, UIAbility, Want } from '@kit.AbilityKit';
import { hilog } from '@kit.PerformanceAnalysisKit';
import { window } from '@kit.ArkUI';
import { RdbService } from 'common';

const TAG: string = 'EntryAbility';
const DOMAIN: number = 0x0001;

export default class EntryAbility extends UIAbility {
  onCreate(want: Want, launchParam: AbilityConstant.LaunchParam): void {
    this.context.getApplicationContext().setColorMode(
      ConfigurationConstant.ColorMode.COLOR_MODE_NOT_SET
    );
    hilog.info(DOMAIN, TAG, 'Ability onCreate');
  }

  onDestroy(): void {
    hilog.info(DOMAIN, TAG, 'Ability onDestroy');
  }

  onWindowStageCreate(windowStage: window.WindowStage): void {
    hilog.info(DOMAIN, TAG, 'Ability onWindowStageCreate');

    windowStage.getMainWindow().then((windowClass: window.Window) => {
      // ① 窗口布局配置(非全屏模式,系统自动处理状态栏安全区域)
      windowClass.setWindowLayoutFullScreen(false).then(() => {
        hilog.info(DOMAIN, TAG, 'Window layout configured');
      });

      // ② 安全区域采集
      const navArea = windowClass.getWindowAvoidArea(
        window.AvoidAreaType.TYPE_NAVIGATION_INDICATOR);
      AppStorage.setOrCreate('bottomRectHeight', navArea.bottomRect.height);

      const sysArea = windowClass.getWindowAvoidArea(
        window.AvoidAreaType.TYPE_SYSTEM);
      AppStorage.setOrCreate('topRectHeight', sysArea.topRect.height);
    });

    // ③ RDB 本地存储初始化
    RdbService.getInstance().initStore(this.context);

    // ④ 加载主页面
    windowStage.loadContent('pages/MainPage', (err) => {
      if (err.code) {
        hilog.error(DOMAIN, TAG, 'Failed to load content: %{public}s', JSON.stringify(err));
        return;
      }
      hilog.info(DOMAIN, TAG, 'Succeeded in loading the content');
    });
  }

  onWindowStageDestroy(): void {
    hilog.info(DOMAIN, TAG, 'Ability onWindowStageDestroy');
  }

  onForeground(): void {
    hilog.info(DOMAIN, TAG, 'Ability onForeground');
  }

  onBackground(): void {
    hilog.info(DOMAIN, TAG, 'Ability onBackground');
  }
}

3.1 关键步骤拆解

步骤代码位置说明
① 窗口布局setWindowLayoutFullScreen(false)使用非全屏模式,系统自动处理状态栏安全区域,避免内容被遮挡
② 安全区域采集getWindowAvoidArea() × 2获取顶部系统栏高度和底部导航指示器高度,存入 AppStorage 供全局使用
③ RDB 初始化RdbService.getInstance().initStore()在页面加载前完成本地数据库初始化,确保页面 aboutToAppear 时可立即查询
④ 页面加载loadContent('pages/MainPage')加载入口页面,即下文详述的 MainPage

3.2 颜色模式

this.context.getApplicationContext().setColorMode(
  ConfigurationConstant.ColorMode.COLOR_MODE_NOT_SET
);

COLOR_MODE_NOT_SET 表示跟随系统设置(亮色/暗色自适应),不强制指定。如需锁定亮色模式,可改为 COLOR_MODE_LIGHT


四、HdsNavigation + HdsTabs 沉浸光感框架

MainPage 是整个 App 的核心页面,它使用 HarmonyOS 6.1 新增的 @kit.UIDesignKit 中的 HdsNavigation 和 HdsTabs 组件,构建了一套沉浸光感导航框架。

4.1 完整代码

import {
  HdsNavigation, HdsNavigationTitleMode, HdsNavigationTitleBarOptions,
  HdsTabs, HdsTabsController, hdsMaterial
} from '@kit.UIDesignKit';
import { RouterService, RouteConstants, UserInfo, ThemeConstants } from 'common';
import { HomePage } from 'home';
import { ParkListPage, ParkDetailPage } from 'ParkService';
import { MapPage } from 'MapService';
import { LoginPage, RegisterPage, ProfileEditPage } from 'AccountCenter';
import {
  PersonalCenterPage,
  FavoritesPage, HistoryPage, SettingsPage, WebViewPage
} from 'PersonalCenter';
import { RouterConfig } from '../router/RouterConfig';

@Entry
@ComponentV2
struct MainPage {
  @Local currentTabIndex: number = 0;
  @Local navPathStack: NavPathStack = new NavPathStack();
  @Local topRectHeight: number = 0;
  @Local bottomRectHeight: number = 0;
  @Provider('UserInfo') userInfo: UserInfo = new UserInfo();
  private tabsController: HdsTabsController = new HdsTabsController();

  aboutToAppear(): void {
    RouterService.registerNavPathStack(this.navPathStack);
    RouterConfig.registerAllRoutes();
  }

  @Builder
  routeMapBuilder(name: string, param: object) {
    if (name === RouteConstants.PARK_DETAIL_PAGE) {
      ParkDetailPage({ spotId: (param as Record<string, string>)['spotId'] ?? '' })
    } else if (name === RouteConstants.LOGIN_PAGE) {
      LoginPage()
    } else if (name === RouteConstants.REGISTER_PAGE) {
      RegisterPage()
    } else if (name === RouteConstants.FAVORITES_PAGE) {
      FavoritesPage({ filterType: (param as Record<string, string>)['filterType'] ?? '' })
    } else if (name === RouteConstants.HISTORY_PAGE) {
      HistoryPage()
    } else if (name === RouteConstants.SETTINGS_PAGE) {
      SettingsPage()
    } else if (name === RouteConstants.WEB_VIEW_PAGE) {
      WebViewPage({
        url: (param as Record<string, string>)['url'] ?? '',
        title: (param as Record<string, string>)['title'] ?? ''
      })
    } else if (name === RouteConstants.PROFILE_EDIT_PAGE) {
      ProfileEditPage()
    }
  }

  build() {
    Column() {
      HdsNavigation() {
        HdsTabs({ controller: this.tabsController }) {
          TabContent() {
            Column() {
              HomePage()
            }
            .width('100%')
            .height('100%')
            .justifyContent(FlexAlign.Start)
          }
          .tabBar(this.tabBarBuilder('\uD83C\uDFE0', '\uD83C\uDFE0', '首页', 0))

          TabContent() {
            Column() {
              ParkListPage()
            }
            .width('100%')
            .height('100%')
            .justifyContent(FlexAlign.Start)
          }
          .tabBar(this.tabBarBuilder('\uD83C\uDF3F', '\uD83C\uDF3F', '游园', 1))

          TabContent() {
            Column() {
              MapPage()
            }
            .width('100%')
            .height('100%')
            .justifyContent(FlexAlign.Start)
          }
          .tabBar(this.tabBarBuilder('\uD83D\uDDFA\uFE0F', '\uD83D\uDDFA\uFE0F', '地图', 2))

          TabContent() {
            Column() {
              PersonalCenterPage()
            }
            .width('100%')
            .height('100%')
            .justifyContent(FlexAlign.Start)
          }
          .tabBar(this.tabBarBuilder('\uD83D\uDC64', '\uD83D\uDC64', '我的', 3))
        }
        .barOverlap(true)
        .vertical(false)
        .barPosition(BarPosition.End)
        .barFloatingStyle({
          barBottomMargin: 28,
          systemMaterialEffect: {
            materialType: hdsMaterial.MaterialType.ADAPTIVE,
            materialLevel: hdsMaterial.MaterialLevel.ADAPTIVE
          }
        })
        .onChange((index: number) => {
          this.currentTabIndex = index;
        })
      }
      .mode(NavigationMode.Stack)
      .navDestination(this.routeMapBuilder)
      .titleBar({
        content: { title: { mainTitle: '广州旅游住宿' } },
        style: {
          systemMaterialEffect: {
            materialType: hdsMaterial.MaterialType.ADAPTIVE,
            materialLevel: hdsMaterial.MaterialLevel.ADAPTIVE
          }
        }
      } as HdsNavigationTitleBarOptions)
      .titleMode(HdsNavigationTitleMode.MINI)
      .hideBackButton(true)
    }
    .width('100%')
    .height('100%')
  }

  @Builder
  tabBarBuilder(normalEmoji: string, selectedEmoji: string, label: string, index: number) {
    Column() {
      Text(this.currentTabIndex === index ? selectedEmoji : normalEmoji)
        .fontSize(22)
        .fontColor(this.currentTabIndex === index ? '#1A73E8' : '#5F6368')
      Text(label)
        .fontSize(10)
        .fontColor(this.currentTabIndex === index ? '#1A73E8' : '#5F6368')
        .margin({ top: 2 })
    }
    .width('100%')
    .justifyContent(FlexAlign.Center)
    .onClick(() => {
      this.currentTabIndex = index;
      this.tabsController.changeIndex(index);
    })
  }
}

4.2 HdsNavigation 导航容器

HdsNavigation@kit.UIDesignKit 提供的高阶导航容器,等价于 Navigation 组件的 HDS(HarmonyOS Design System)增强版:

HdsNavigation() {
  // 内部放置 HdsTabs
}
.mode(NavigationMode.Stack)           // 栈式导航,支持 push/pop
.navDestination(this.routeMapBuilder)  // 路由目标页面构建器
.titleBar({
  content: { title: { mainTitle: '广州旅游住宿' } },
  style: {
    systemMaterialEffect: {
      materialType: hdsMaterial.MaterialType.ADAPTIVE,
      materialLevel: hdsMaterial.MaterialLevel.ADAPTIVE
    }
  }
} as HdsNavigationTitleBarOptions)
.titleMode(HdsNavigationTitleMode.MINI)
.hideBackButton(true)                 // 隐藏标题栏左侧返回按钮

核心配置

属性说明
modeNavigationMode.Stack栈式导航,新页面从右侧滑入,返回时 pop 出栈
navDestinationrouteMapBuilder路由分发 Builder,根据路由名匹配页面
titleBar.contentmainTitle: '广州旅游住宿'顶部标题栏文字
titleBar.style.systemMaterialEffectADAPTIVE标题栏使用自适应光感材质,滚动时呈现毛玻璃效果
titleBar 类型断言as HdsNavigationTitleBarOptionsArkTS 严格模式要求对象字面量必须对应显式类型
titleModeMINI始终显示小型标题栏,不随滚动展开/收缩
hideBackButtontrue隐藏标题栏左侧的默认返回按钮,主页面不需要返回操作

4.3 HdsTabs 底部导航 + 浮动样式

HdsTabs({ controller: this.tabsController }) {
  // 四个 TabContent
}
.barOverlap(true)
.vertical(false)
.barPosition(BarPosition.End)
.barFloatingStyle({
  barBottomMargin: 28,
  systemMaterialEffect: {
    materialType: hdsMaterial.MaterialType.ADAPTIVE,
    materialLevel: hdsMaterial.MaterialLevel.ADAPTIVE
  }
})

浮动导航栏是本项目的视觉亮点:

  • barOverlap(true):TabBar 浮在内容之上,不占用页面空间。
  • barFloatingStyle:启用浮动模式,barBottomMargin: 28 让 TabBar 距离屏幕底部 28vp,形成"胶囊"效果。
  • systemMaterialEffect:使用 hdsMaterial.MaterialType.ADAPTIVE 自适应材质——TabBar 背景会根据底层内容自动调整明暗,实现类似 iOS 毛玻璃的通透感。

4.4 四个 TabContent

TabContent() {
  Column() {
    HomePage()
  }
  .width('100%')
  .height('100%')
  .justifyContent(FlexAlign.Start)
}
.tabBar(this.tabBarBuilder('\uD83C\uDFE0', '\uD83C\uDFE0', '首页', 0))

TabContent() {
  Column() {
    ParkListPage()
  }
  .width('100%')
  .height('100%')
  .justifyContent(FlexAlign.Start)
}
.tabBar(this.tabBarBuilder('\uD83C\uDF3F', '\uD83C\uDF3F', '游园', 1))

TabContent() {
  Column() {
    MapPage()
  }
  .width('100%')
  .height('100%')
  .justifyContent(FlexAlign.Start)
}
.tabBar(this.tabBarBuilder('\uD83D\uDDFA\uFE0F', '\uD83D\uDDFA\uFE0F', '地图', 2))

TabContent() {
  Column() {
    PersonalCenterPage()
  }
  .width('100%')
  .height('100%')
  .justifyContent(FlexAlign.Start)
}
.tabBar(this.tabBarBuilder('\uD83D\uDC64', '\uD83D\uDC64', '我的', 3))

每个 TabContent 内部用 Column 包裹页面组件,并设置 .justifyContent(FlexAlign.Start) 确保内容顶部对齐,避免 TabContent 默认垂直居中导致页面内容下沉。各页面组件直接实例化对应 feature 模块的入口页面:

  • HomePagehome 模块
  • ParkListPageParkService 模块
  • MapPageMapService 模块
  • PersonalCenterPagePersonalCenter 模块

4.5 自定义 tabBarBuilder

重要:HarmonyOS 6.1 中 sys.symbol.*sys.media.ohos_ic_public_* 系统图标资源大量废弃或不可用。本项目采用 Unicode emoji 文本 替代所有系统图标,确保在所有设备上稳定显示。

@Builder
tabBarBuilder(normalEmoji: string, selectedEmoji: string, label: string, index: number) {
  Column() {
    Text(this.currentTabIndex === index ? selectedEmoji : normalEmoji)
      .fontSize(22)
      .fontColor(this.currentTabIndex === index ? '#1A73E8' : '#5F6368')
    Text(label)
      .fontSize(10)
      .fontColor(this.currentTabIndex === index ? '#1A73E8' : '#5F6368')
      .margin({ top: 2 })
  }
  .width('100%')
  .justifyContent(FlexAlign.Center)
  .onClick(() => {
    this.currentTabIndex = index;
    this.tabsController.changeIndex(index);
  })
}

Tab 图标映射

TabEmojiUnicode
首页🏠\uD83C\uDFE0
游园🌿\uD83C\uDF3F
地图🗺️\uD83D\uDDFA\uFE0F
我的👤\uD83D\uDC64

选中态切换逻辑

  • 颜色切换:选中 #1A73E8(品牌蓝),未选中 #5F6368(中性灰)。
  • tabsController.changeIndex(index):通过控制器驱动 Tab 切换,与 @Local currentTabIndex 双向同步。

4.6 @Provider(UserInfo) 全局状态注入

@Provider('UserInfo') userInfo: UserInfo = new UserInfo();

@Provider@ComponentV2 体系中的跨层级状态共享装饰器。在 MainPage 中声明后,所有子组件(包括深层嵌套的 feature 页面)都可以通过 @Consumer('UserInfo') 获取用户信息,无需逐层 @Prop 传递。

注意@Provider@Consumer 的参数必须使用字符串字面量(如 'UserInfo'),不能直接使用类型名。这是 ArkTS 严格模式的要求。

// 子组件中使用
@ComponentV2
struct UserAvatar {
  @Consumer('UserInfo') userInfo: UserInfo = new UserInfo();
  // 直接使用 this.userInfo.nickname 等属性
}

五、路由注册与分发

5.1 RouterConfig 路由表

重要:ArkTS 严格模式下,@Builder 装饰器不能用于 class 内部,只能在全局作用域或 struct 中使用。因此路由页面的构建改为在 MainPagerouteMapBuilder 中内联实现。

import { RouteConstants } from 'common';

export class RouterConfig {
  static registerAllRoutes(): void {
    // 路由页面通过 MainPage 的 navDestination Builder 直接构建
  }
}

RouterConfig 简化为仅保留 registerAllRoutes() 方法(预留扩展),所有页面构建逻辑统一在 MainPage.routeMapBuilder 中内联实现:

@Builder
routeMapBuilder(name: string, param: object) {
  if (name === RouteConstants.PARK_DETAIL_PAGE) {
    ParkDetailPage({ spotId: (param as Record<string, string>)['spotId'] ?? '' })
  } else if (name === RouteConstants.LOGIN_PAGE) {
    LoginPage()
  }
  // ... 其他路由
}

5.2 路由分发流程

路由分发的完整链路如下:

feature 模块调用 RouterService.push(路由名, 参数)
    ↓
RouterService 内部调用 NavPathStack.pushByName(路由名, 参数)
    ↓
HdsNavigation 的 navDestination 触发 routeMapBuilder
    ↓
routeMapBuilder 根据路由名直接内联构建页面组件

以“查看景点详情”为例:

// 1. 在 ScenicCard 的点击回调中(home 模块)
RouterService.push(RouteConstants.PARK_DETAIL_PAGE, { spotId: 'gz-001' });

// 2. routeMapBuilder 直接构建页面(无需调用 RouterConfig)
if (name === RouteConstants.PARK_DETAIL_PAGE) {
  ParkDetailPage({ spotId: (param as Record<string, string>)['spotId'] ?? '' })
}

参数传递约定:所有路由参数统一使用 Record<string, string> 格式,通过 (param as Record<string, string>)['key'] 取值,并用 ?? '' 提供默认值防止 undefined。


六、安全区域处理

全屏模式下,页面内容会延伸到状态栏和底部导航指示器区域,因此需要精确获取安全区域高度,避免内容被系统 UI 遮挡。

6.1 EntryAbility 中的采集

// 底部导航指示器高度
const navArea = windowClass.getWindowAvoidArea(
  window.AvoidAreaType.TYPE_NAVIGATION_INDICATOR);
AppStorage.setOrCreate('bottomRectHeight', navArea.bottomRect.height);

// 顶部系统栏高度(状态栏 + 挖孔区域)
const sysArea = windowClass.getWindowAvoidArea(
  window.AvoidAreaType.TYPE_SYSTEM);
AppStorage.setOrCreate('topRectHeight', sysArea.topRect.height);

6.2 MainPage 中的消费

由于采用非全屏模式(setWindowLayoutFullScreen(false)),系统自动处理状态栏安全区域,MainPage 无需手动添加顶部 padding。AppStorage 中的 topRectHeightbottomRectHeight 仍可供 feature 模块的页面使用,用于自定义标题栏或底部操作栏的 padding 计算。

// feature 页面中可按需获取
const topHeight = AppStorage.get<number>('topRectHeight');

七、关键注意事项

7.1 NavPathStack 注册时机

aboutToAppear(): void {
  RouterService.registerNavPathStack(this.navPathStack);
  RouterConfig.registerAllRoutes();
}

NavPathStack 必须在 aboutToAppear 中注册到 RouterService,而不是在构造函数或 build() 中。原因:

  • aboutToAppear 在组件挂载前调用,此时 NavPathStack 实例已创建。
  • 如果在 build() 中注册,每次 UI 刷新都会重复注册,导致路由栈异常。
  • 注册顺序:先注册 NavPathStack(让 RouterService 持有栈引用),再注册路由表(声明可导航页面列表)。

7.2 路由 Builder 内联构建

注意:ArkTS 严格模式下,@Builder 装饰器只能用于全局作用域或 struct(组件)中,不能用于普通 class 的静态方法。因此路由页面的构建不能放在 RouterConfig 类中,而是改为在 MainPagerouteMapBuilder 中直接内联构建:

@Builder
routeMapBuilder(name: string, param: object) {
  if (name === RouteConstants.PARK_DETAIL_PAGE) {
    ParkDetailPage({ spotId: (param as Record<string, string>)['spotId'] ?? '' })
  } else if (name === RouteConstants.LOGIN_PAGE) {
    LoginPage()
  }
  // ...
}

这种内联方式完全符合 ArkTS 严格模式要求,且路由参数直接传递给页面组件,无需中间转发。

7.3 @ComponentV2 + @Entry

@Entry
@ComponentV2
struct MainPage {

@Entry 标记此组件为页面入口(对应 module.json5pages 配置的页面路径),@ComponentV2 启用 V2 装饰器体系。两者必须同时使用:

  • 仅有 @Entry 没有 @ComponentV2:使用 V1 装饰器体系(@State@Prop 等)。
  • 仅有 @ComponentV2 没有 @Entry:该组件是子组件,不能作为页面入口。
  • @Entry + @ComponentV2:V2 体系下的页面入口,支持 @Local@Param@Event@Provider/@Consumer 等新版装饰器。

7.4 HdsTabsController 与 @Local 的协作

@Local currentTabIndex: number = 0;
private tabsController: HdsTabsController = new HdsTabsController();
  • currentTabIndex@Local 声明为组件本地状态,变更时触发 UI 刷新(tabBarBuilder 中的选中态切换依赖此变量)。
  • tabsControllerprivate 声明(非 @Local),因为控制器本身不需要触发 UI 刷新——它只是一个"命令式"操作的句柄(如 tabsController.changeIndex(index))。

附:页面路由注册文件

resources/base/profile/main_pages.json 中注册所有可导航页面:

{
  "src": [
    "pages/MainPage"
  ]
}

由于本项目使用 HdsNavigation + navDestination 的动态路由分发模式,只需注册 MainPage 一个页面。其他页面(景点详情、登录、注册等)都通过 routeMapBuilder 动态构建,无需在 main_pages.json 中声明。


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值