在构建“美寇商城”这类复杂鸿蒙应用时,清晰、高效且可维护的页面导航和路由管理是架构设计的核心。随着鸿蒙5.0对开发范式的持续演进,一种基于HmRouter的集中式路由方案,因其在解耦、拦截与统一管理方面的卓越表现,正成为构建大型应用的新范式。

本文将深入探讨如何利用HmRouter,为美寇商城构建一套从页面跳转到全局拦截的完整路由解决方案。

一、 为什么选择HmRouter:从分散跳转到集中管控

在传统或基础的导航模式中,页面跳转逻辑(如router.pushUrl)常常直接散落在各个业务组件的按钮点击事件中。这会导致几个显著问题:

  1. 紧耦合:组件需要知道目标页面的具体路径,路径变更可能引发多处修改。
  2. 难以维护:路由逻辑分散,统一管理路由行为(如日志、埋点、权限)非常困难。
  3. 安全性弱:缺乏统一的入口进行路由拦截(如登录检查、支付状态验证)。

HmRouter的设计理念正是为了解决这些问题。它通过引入一个集中式的路由配置中心,将所有路由规则、路径映射和拦截逻辑收拢管理,让业务组件只需声明“意图”,而由路由中心负责“执行”与“管控”。

下图清晰地展示了这两种模式的架构差异与流程对比:

“问题: 逻辑分散
难以统一拦截”

“优势: 逻辑集中
易于扩展和维护”

基于HmRouter的集中式路由

“1. 发起‘NAV_TO_DETAIL’意图”

“1. 发起‘NAV_TO_CART’意图”

“2. 检查登录状态等拦截器”

“3. 路径转换与统一处理”

商品组件

HmRouter
路由配置与拦截中心

购物车组件

系统执行跳转

传统分散式路由

“直接调用 router.pushUrl(‘/detail’)”

“直接调用 router.pushUrl(‘/cart’)”

商品组件

路由API

购物车组件

系统执行跳转

二、 美寇商城HmRouter实战:从配置到跳转

2.1 项目结构与核心概念

我们首先规划与HmRouter相关的项目目录结构:

src/main/ets/
├── router/                  # 路由核心目录
│   ├── HmRouter.ets        # HmRouter单例类,路由中枢
│   ├── RouterConfig.ets    # 路由路径、名称等常量配置
│   ├── RouterInterceptor.ets # 路由拦截器定义
│   └── types/              # 路由相关类型定义
│       └── RouterParam.ets
├── pages/                  # 所有页面组件
│   ├── HomePage.ets
│   ├── GoodsDetailPage.ets
│   └── ...
└── MainPage.ets           # 应用入口,初始化路由

2.2 核心代码实现

步骤一:定义路由配置 (RouterConfig.ets)
这是路由的“地图”,定义了所有可通达的目的地。

// src/main/ets/router/RouterConfig.ets
/**
 * 美寇商城主路由配置
 * 使用枚举和常量确保路径唯一性,避免魔法字符串
 */
export enum RouterPages {
  HOME = '/home',
  CATEGORY = '/category',
  GOODS_DETAIL = '/detail',
  CART = '/cart',
  PAYMENT = '/payment',
  USER_LOGIN = '/user/login'
}

/**
 * 路由参数类型定义接口
 * 为每个需要参数的页面声明强类型的参数结构
 */
export interface GoodsDetailParams {
  goodsId: string; // 商品ID
  spuCode?: string; // 可选SPU编码
}

export interface PaymentParams {
  orderId: string;
  totalAmount: number;
}

// 路由名称到路径的映射,可用于更语义化的跳转
export const RouteAlias: Record<string, RouterPages> = {
  'MAIN_HOME': RouterPages.HOME,
  'PRODUCT_DETAIL': RouterPages.GOODS_DETAIL,
  'SHOPPING_CART': RouterPages.CART,
  'ORDER_PAY': RouterPages.PAYMENT
} as const;

步骤二:实现HmRouter核心中枢 (HmRouter.ets)
这是路由系统的“大脑”,负责调度所有跳转请求和拦截逻辑。

// src/main/ets/router/HmRouter.ets
import { RouterPages, GoodsDetailParams, PaymentParams } from './RouterConfig';
import { RouterInterceptor, InterceptorChain } from './RouterInterceptor';
import router from '@ohos.router';

/**
 * 美寇商城路由管理器(单例模式)
 * 1. 提供统一跳转API
 * 2. 管理拦截器链
 * 3. 处理路由参数序列化/反序列化
 */
export class HmRouter {
  private static instance: HmRouter;
  private interceptors: RouterInterceptor[] = []; // 拦截器数组

  private constructor() {
    // 私有构造函数,初始化时可注册全局拦截器
    this.registerGlobalInterceptors();
  }

  static getInstance(): HmRouter {
    if (!HmRouter.instance) {
      HmRouter.instance = new HmRouter();
    }
    return HmRouter.instance;
  }

  /**
   * 核心跳转方法:携带参数跳转到商品详情页
   * @param params 商品参数
   * @param extra 额外配置(如跳转动画)
   */
  async navigateToGoodsDetail(params: GoodsDetailParams, extra?: router.RouterOptions): Promise<void> {
    const targetUrl: string = RouterPages.GOODS_DETAIL;
    const routerContext: RouterContext<GoodsDetailParams> = {
      url: targetUrl,
      params,
      extra,
      timestamp: new Date().getTime()
    };

    // 创建拦截器链并执行
    const chain = new InterceptorChain([...this.interceptors]);
    const shouldProceed = await chain.proceed(routerContext);

    if (shouldProceed) {
      // 所有拦截器通过,执行实际跳转
      try {
        await router.pushUrl({
          url: targetUrl,
          params: params // 参数会自动序列化传递
        }, extra);
        this.logRouteSuccess(routerContext);
      } catch (error) {
        console.error(`[HmRouter] 跳转到 ${targetUrl} 失败:`, error);
        this.handleNavigationError(error, routerContext);
      }
    } else {
      console.warn(`[HmRouter] 跳转至 ${targetUrl} 被拦截`);
    }
  }

  /**
   * 跳转到支付页(这是一个需要重点拦截的路径)
   */
  async navigateToPayment(params: PaymentParams): Promise<void> {
    const context: RouterContext<PaymentParams> = {
      url: RouterPages.PAYMENT,
      params,
      timestamp: new Date().getTime()
    };
    const chain = new InterceptorChain([...this.interceptors]);
    if (await chain.proceed(context)) {
      await router.pushUrl({ url: RouterPages.PAYMENT, params });
    }
  }

  /**
   * 返回首页,并清空历史栈
   */
  async navigateHomeWithClear(): Promise<void> {
    await router.clear({ url: RouterPages.HOME });
  }

  /**
   * 注册全局拦截器
   */
  private registerGlobalInterceptors(): void {
    // 按顺序注册拦截器,顺序可能影响逻辑
    this.addInterceptor(new LoginInterceptor());
    this.addInterceptor(new PaymentStatusInterceptor());
    this.addInterceptor(new LoggingInterceptor());
  }

  /**
   * 动态添加拦截器(也可用于特定模块)
   */
  addInterceptor(interceptor: RouterInterceptor): void {
    this.interceptors.push(interceptor);
  }

  private logRouteSuccess(context: RouterContext<any>): void {
    // 实际项目中可接入埋点系统
    console.log(`[路由成功] ${context.url}`, context);
  }

  private handleNavigationError(error: Error, context: RouterContext<any>): void {
    // 统一错误处理,例如跳转到网络错误页
    console.error('路由错误处理:', error);
  }
}

/**
 * 路由上下文,在拦截器链中传递
 */
export interface RouterContext<T = any> {
  url: string;
  params: T;
  extra?: router.RouterOptions;
  timestamp: number;
  // 可根据需要扩展,例如携带来源页面信息
  from?: string;
}

步骤三:实现路由拦截器 (RouterInterceptor.ets)
拦截器是HmRouter的“守卫”,负责在跳转前进行校验和处理。

// src/main/ets/router/RouterInterceptor.ets
import { RouterPages, PaymentParams } from './RouterConfig';
import { RouterContext } from './HmRouter';
import { UserSession } from '../model/UserSession'; // 假设的用户会话管理

/**
 * 拦截器抽象接口
 */
export interface RouterInterceptor {
  intercept(context: RouterContext, chain: InterceptorChain): Promise<boolean>;
}

/**
 * 拦截器链,负责按顺序执行拦截器
 */
export class InterceptorChain {
  private interceptors: RouterInterceptor[];
  private index: number = 0;

  constructor(interceptors: RouterInterceptor[]) {
    this.interceptors = [...interceptors];
  }

  async proceed(context: RouterContext): Promise<boolean> {
    if (this.index >= this.interceptors.length) {
      return true; // 所有拦截器已执行完毕,允许通过
    }
    const interceptor = this.interceptors[this.index++];
    return interceptor.intercept(context, this);
  }
}

/**
 * 登录状态拦截器
 * 检查需要登录的页面,如果未登录则跳转到登录页
 */
export class LoginInterceptor implements RouterInterceptor {
  // 定义需要登录才能访问的页面白名单
  private requireLoginPages: Set<string> = new Set([
    RouterPages.CART,
    RouterPages.PAYMENT,
    RouterPages.USER_CENTER // 假设的个人中心
  ]);

  async intercept(context: RouterContext, chain: InterceptorChain): Promise<boolean> {
    if (this.requireLoginPages.has(context.url) && !UserSession.isLoggedIn()) {
      // 未登录,拦截并重定向到登录页,同时携带原目标信息以便登录后回跳
      console.log('[登录拦截器] 未登录,跳转到登录页');
      const loginRedirectParams = {
        redirectUrl: context.url,
        redirectParams: JSON.stringify(context.params)
      };
      await router.pushUrl({
        url: RouterPages.USER_LOGIN,
        params: loginRedirectParams
      });
      return false; // 中断原始跳转
    }
    // 已登录或无需登录,继续下一个拦截器
    return chain.proceed(context);
  }
}

/**
 * 支付状态拦截器
 * 在跳转到支付页前,验证订单状态是否有效
 */
export class PaymentStatusInterceptor implements RouterInterceptor {
  async intercept(context: RouterContext<PaymentParams>, chain: InterceptorChain): Promise<boolean> {
    if (context.url === RouterPages.PAYMENT) {
      const { orderId } = context.params;
      // 模拟异步验证订单状态
      const isValid = await this.validateOrder(orderId);
      if (!isValid) {
        // 订单无效,可以跳转到订单失效提示页
        await router.pushUrl({ url: '/error/order-invalid' });
        return false;
      }
    }
    return chain.proceed(context);
  }

  private async validateOrder(orderId: string): Promise<boolean> {
    // 此处应发起网络请求验证订单
    return new Promise((resolve) => setTimeout(() => resolve(orderId.startsWith('VALID')), 50));
  }
}

/**
 * 日志记录拦截器
 * 记录所有路由跳转行为,用于埋点和调试
 */
export class LoggingInterceptor implements RouterInterceptor {
  async intercept(context: RouterContext, chain: InterceptorChain): Promise<boolean> {
    console.log(`[路由日志] 开始跳转: ${context.url}`, {
      params: context.params,
      timestamp: new Date(context.timestamp).toISOString()
    });
    const startTime = Date.now();
    const result = await chain.proceed(context);
    const duration = Date.now() - startTime;
    console.log(`[路由日志] 跳转处理完成: ${context.url}, 耗时: ${duration}ms, 结果: ${result ? '通过' : '拦截'}`);
    return result;
  }
}

步骤四:在业务组件中使用 (GoodsListComponent.ets)
业务组件从此不再关心具体路径和跳转逻辑,只需通过HmRouter发起导航意图。

// src/main/ets/components/GoodsListComponent.ets
import { HmRouter } from '../router/HmRouter';
import { GoodsItem } from '../model/GoodsItem';

@Component
struct GoodsItemComponent {
  @Prop goodsItem: GoodsItem;

  build() {
    Column() {
      Image(this.goodsItem.coverUrl)
        .onClick(() => {
          // 点击商品,通过HmRouter跳转到详情页
          // 业务组件只传递数据,不关心目标路径和跳转细节
          HmRouter.getInstance().navigateToGoodsDetail({
            goodsId: this.goodsItem.id,
            spuCode: this.goodsItem.spuCode
          }, { /* 可配置动画等参数 */ });
        })
    }
  }
}

步骤五:在页面中接收参数 (GoodsDetailPage.ets)
目标页面以类型安全的方式接收路由参数。

// src/main/ets/pages/GoodsDetailPage.ets
import { GoodsDetailParams } from '../router/RouterConfig';

@Component
export struct GoodsDetailPage {
  // 页面内通过router.getParams()获取参数
  private goodsId: string = '';
  private spuCode: string = '';

  aboutToAppear(): void {
    const params = router.getParams() as GoodsDetailParams;
    if (params?.goodsId) {
      this.goodsId = params.goodsId;
      this.spuCode = params.spuCode || '';
      this.loadGoodsDetail();
    } else {
      // 参数错误,可返回或提示
      router.back();
    }
  }

  private loadGoodsDetail(): void {
    // 根据goodsId加载商品详情...
  }
}

三、 HmRouter在美寇商城中的高级应用场景

3.1 与“一多适配”结合

利用HmRouter的集中配置,可以轻松实现不同设备上的差异化路由。例如,在平板上,商品详情页可能以弹窗或分屏形式展示,而非全屏跳转。

// 在HmRouter中扩展设备相关的路由逻辑
async navigateToGoodsDetail(params: GoodsDetailParams) {
  const targetUrl = RouterPages.GOODS_DETAIL;
  const context = { url: targetUrl, params };

  if (await this.isTablet() && this.supportsSplitView()) {
    // 平板设备且支持分屏,跳转到分屏模式URL
    await router.pushUrl({ url: `${targetUrl}?displayMode=split`, params });
  } else {
    // 手机设备,正常全屏跳转
    await router.pushUrl({ url: targetUrl, params });
  }
}

3.2 支付流程的强拦截与状态保障

支付流程是电商的核心,涉及多次严格的状态校验。利用HmRouter的拦截器链,我们可以构建一个健壮的支付路由流程。

// 一个简化的支付路由流程示例
export class PaymentRouteHandler {
  static async startPaymentFlow(orderId: string): Promise<void> {
    const router = HmRouter.getInstance();
    // 1. 尝试跳转支付页,会自动触发登录、订单状态等拦截器
    try {
      await router.navigateToPayment({ orderId, totalAmount: 99.9 });
    } catch (error) {
      // 2. 处理支付流程中的异常(如网络错误、支付取消)
      await router.navigateToPaymentResult('failed');
    }
    // 3. 支付成功后的处理(可由支付页面回调触发)
    // await router.navigateToPaymentResult('success');
  }
}

3.3 路由降级与容错

当目标页面因版本或配置问题无法打开时,HmRouter可以提供降级方案。

async navigateWithFallback(context: RouterContext, fallbackUrl: string): Promise<void> {
  try {
    await router.pushUrl({ url: context.url, params: context.params });
  } catch (error) {
    if (error.code === '页面不存在') {
      console.warn(`目标页面 ${context.url} 不可用,降级到 ${fallbackUrl}`);
      await router.pushUrl({ url: fallbackUrl });
    } else {
      throw error;
    }
  }
}

四、 总结

通过为“美寇商城”引入基于HmRouter的集中式路由架构,我们获得了以下显著优势:

  1. 彻底解耦:业务组件与具体页面路径分离,提高了代码的复用性和可维护性。
  2. 强大的中心化管控:在唯一的路由中枢,可以统一实施登录验证、权限检查、业务状态校验、日志埋点、异常处理等全局策略。
  3. 卓越的可扩展性:通过拦截器链模式,可以非侵入式地添加新的路由控制逻辑,符合开放-封闭原则。
  4. 类型安全与智能提示:集中的路由配置和参数类型定义,配合TypeScript,能在开发阶段提供良好的类型检查和代码提示,减少运行时错误。

HmRouter不仅仅是一个工具类,它更代表了一种将路由视为应用基础设施的架构思想。在鸿蒙5.0开发复杂应用时,尽早采用此类范式,将为“美寇商城”应对未来持续增长的业务复杂度,打下坚实、灵活的基础。

Logo

作为“人工智能6S店”的官方数字引擎,为AI开发者与企业提供一个覆盖软硬件全栈、一站式门户。

更多推荐