鸿蒙APP开发:页面路由与组件跳转详解
鸿蒙APP开发中的页面路由与组件跳转技术,涵盖路由配置、参数传递、全局状态管理和高级特性如预加载、动画及拦截器的应用,通过实际案例演示如何实现完整的页面导航流程。

鸿蒙APP开发中的页面路由与组件跳转技术,涵盖路由配置、参数传递、全局状态管理和高级特性如预加载、动画及拦截器的应用,通过实际案例演示如何实现完整的页面导航流程。

这是《鸿蒙APP开发从入门到精通》的第5篇——架构导航篇,承接第4篇的「网络请求与数据持久化」,100%复用项目架构,为后续电商购物车全栈项目铺垫页面导航和组件间通信的核心技术。
学习目标:
学习重点:
HarmonyOS Next 提供了官方页面路由库,支持以下功能:
在 entry/src/main/module.json5 中配置页面路由:
{
"module": {
"pages": [
"pages/Index",
"pages/SearchPage",
"pages/ProductDetailPage",
"pages/CartPage",
"pages/OrderPage",
"pages/PaymentPage",
"pages/LoginPage",
"pages/MyPage"
]
}
}
⌨️ entry/src/main/ets/utils/RouterUtils.ets
import router from '@ohos.router';
// 页面路由参数类型
export interface RouterParams {
[key: string]: any;
}
// 页面路由工具类
export class RouterUtils {
// 跳转到指定页面
static push(url: string, params?: RouterParams): void {
router.pushUrl({ url, params });
}
// 跳转到指定页面并替换当前页面
static replace(url: string, params?: RouterParams): void {
router.replaceUrl({ url, params });
}
// 返回到上一页
static pop(): void {
router.back();
}
// 返回到指定页面
static popTo(url: string): void {
router.backTo({ url });
}
// 重新加载应用
static relaunch(): void {
router.relaunch({ url: 'pages/Index' });
}
// 获取当前页面参数
static getParams(): RouterParams {
return router.getParams() || {};
}
// 获取当前页面栈
static getPages(): Array<router.RouterState> {
return router.getPages();
}
// 清空页面栈
static clear(): void {
router.clear();
}
}
基于第4篇的「MyFirstHarmonyApp」项目架构,实现以下功能:
⌨️ entry/src/main/ets/components/GoodsListComponent.ets(修改GoodsItemComponent)
import { RouterUtils } from '../utils/RouterUtils';
@Component
struct GoodsItemComponent {
@Prop goods: GoodsModel;
build() {
Column({ space: 12 }) {
// 商品图片
Image(this.goods.imageUrl)
.width('100%')
.height(200)
.objectFit(ImageFit.Cover)
.borderRadius(12)
.onClick(() => {
RouterUtils.push('pages/ProductDetailPage', { id: this.goods.id });
});
// 商品信息
// ...
}
}
}
⌨️ entry/src/main/ets/pages/ProductDetailPage.ets(修改)
import { RouterUtils } from '../utils/RouterUtils';
import { CartService } from '../services/CartService';
@Entry
@Component
struct ProductDetailPage {
@State goods: GoodsModel = goodsData[0];
@State count: number = 1;
build() {
Scroll() {
Column({ space: 24 }) {
// 商品图片
// ...
// 商品信息
Column({ space: 16 }) {
// ...
// 加入购物车与立即购买按钮
Row({ space: 16 }) {
Button('加入购物车')
.width('50%')
.height(48)
.backgroundColor('#FFFFFF')
.textColor('#007DFF')
.border({ width: 1, color: '#007DFF' })
.onClick(() => {
CartService.getInstance().addToCart(this.goods.id, this.count);
RouterUtils.push('pages/CartPage');
});
Button('立即购买')
.width('50%')
.height(48)
.backgroundColor('#007DFF')
.onClick(() => {
RouterUtils.push('pages/OrderPage', {
productId: this.goods.id,
count: this.count
});
});
}
.width('100%')
.justifyContent(FlexAlign.SpaceBetween);
}
.width('100%')
.padding(24);
}
.width('100%')
}
.width('100%')
.height('100%')
.backgroundColor('#F5F5F5');
}
}
⌨️ entry/src/main/ets/components/CartComponent.ets(修改)
import { RouterUtils } from '../utils/RouterUtils';
@Component
export struct CartComponent {
@ObjectLink cartItems: Array<CartItemModel> = [];
// 计算选中商品信息
@Computed get checkedItems(): Array<CartItemModel> {
return this.cartItems.filter(item => item.isChecked);
}
build() {
Column({ space: 0 }) {
// 购物车商品列表
// ...
// 购物车底部栏
Row({ space: 16 }) {
// ...
// 总价与结算按钮
Row({ space: 16 }) {
Text(`¥${this.totalPrice.toFixed(2)}`)
.fontSize(18)
.fontWeight(FontWeight.Bold)
.textColor('#FF0000');
Button('结算')
.width(120)
.height(48)
.backgroundColor('#007DFF')
.onClick(() => {
RouterUtils.push('pages/OrderPage', { cartItems: this.checkedItems });
});
}
.layoutWeight(1)
.justifyContent(FlexAlign.End);
}
.width('100%')
.height(56)
.padding(16)
.backgroundColor('#FFFFFF')
.borderRadius(16, 16, 0, 0);
}
.width('100%')
.height('100%')
.backgroundColor('#F5F5F5');
}
}
⌨️ entry/src/main/ets/pages/OrderPage.ets
import { RouterUtils } from '../utils/RouterUtils';
import { CartService } from '../services/CartService';
import { CartItemModel } from '../components/CartComponent';
@Entry
@Component
struct OrderPage {
@State orderId: string = '123456';
@State orderAmount: number = 0;
@State cartItems: Array<CartItemModel> = [];
build() {
Column({ space: 0 }) {
// 订单信息
List({ space: 16 }) {
ForEach(this.cartItems, (item: CartItemModel) => {
ListItem() {
Row({ space: 16 }) {
Image(item.imageUrl)
.width(80)
.height(80)
.objectFit(ImageFit.Contain);
Column({ space: 8 }) {
Text(item.name)
.fontSize(14)
.fontWeight(FontWeight.Bold)
.textColor('#000000');
Text(`¥${item.price} x ${item.count}`)
.fontSize(14)
.textColor('#666666');
}
.layoutWeight(1);
}
.width('100%')
.height('auto');
}
.width('100%')
.height('auto');
}, (item: CartItemModel) => item.id.toString());
}
.width('100%')
.height('auto')
.padding(16)
.layoutWeight(1);
// 订单总金额与支付按钮
Row({ space: 16 }) {
Text(`总金额:¥${this.orderAmount.toFixed(2)}`)
.fontSize(18)
.fontWeight(FontWeight.Bold)
.textColor('#FF0000');
Button('立即支付')
.width(120)
.height(48)
.backgroundColor('#007DFF')
.onClick(() => {
RouterUtils.push('pages/PaymentPage', {
orderId: this.orderId,
orderAmount: this.orderAmount
});
});
}
.width('100%')
.height(56)
.padding(16)
.backgroundColor('#FFFFFF')
.borderRadius(16, 16, 0, 0);
}
.width('100%')
.height('100%')
.backgroundColor('#F5F5F5');
}
aboutToAppear() {
const params = RouterUtils.getParams();
if (params.cartItems) {
this.cartItems = params.cartItems;
this.orderAmount = this.cartItems.reduce(
(total, item) => total + item.price * item.count,
0
);
} else if (params.productId && params.count) {
const product = goodsData.find(item => item.id === params.productId);
if (product) {
this.cartItems = [
new CartItemModel(
product.id,
product.name,
product.imageUrl,
product.price,
params.count,
false
)
];
this.orderAmount = product.price * params.count;
}
}
}
}
⌨️ entry/src/main/ets/pages/PaymentPage.ets
import { RouterUtils } from '../utils/RouterUtils';
@Entry
@Component
struct PaymentPage {
@State orderId: string = '';
@State orderAmount: number = 0;
@State paymentMethod: string = 'alipay';
build() {
Column({ space: 0 }) {
// 支付信息
List({ space: 16 }) {
ListItem() {
Text('订单编号:' + this.orderId)
.fontSize(14)
.textColor('#666666');
}
.width('100%')
.height('auto');
ListItem() {
Text('支付金额:¥' + this.orderAmount.toFixed(2))
.fontSize(18)
.fontWeight(FontWeight.Bold)
.textColor('#FF0000');
}
.width('100%')
.height('auto');
}
.width('100%')
.height('auto')
.padding(16)
.layoutWeight(1);
// 支付方式选择
List({ space: 16 }) {
ListItem() {
Row({ space: 16 }) {
Image($r('app.media.alipay'))
.width(40)
.height(40)
.objectFit(ImageFit.Contain);
Text('支付宝')
.fontSize(14)
.textColor('#000000');
Checkbox()
.checked(this.paymentMethod === 'alipay')
.onChange((isChecked: boolean) => {
if (isChecked) {
this.paymentMethod = 'alipay';
}
});
}
.width('100%')
.height('auto');
}
.width('100%')
.height('auto');
ListItem() {
Row({ space: 16 }) {
Image($r('app.media.wechat'))
.width(40)
.height(40)
.objectFit(ImageFit.Contain);
Text('微信支付')
.fontSize(14)
.textColor('#000000');
Checkbox()
.checked(this.paymentMethod === 'wechat')
.onChange((isChecked: boolean) => {
if (isChecked) {
this.paymentMethod = 'wechat';
}
});
}
.width('100%')
.height('auto');
}
.width('100%')
.height('auto');
}
.width('100%')
.height('auto')
.padding(16);
// 确认支付按钮
Button('确认支付')
.width('100%')
.height(48)
.backgroundColor('#007DFF')
.onClick(() => {
// 模拟支付成功
setTimeout(() => {
RouterUtils.popTo('pages/OrderPage', { paymentResult: 'success' });
}, 1000);
});
}
.width('100%')
.height('100%')
.backgroundColor('#F5F5F5');
}
aboutToAppear() {
const params = RouterUtils.getParams();
if (params.orderId) {
this.orderId = params.orderId;
}
if (params.orderAmount) {
this.orderAmount = params.orderAmount;
}
}
}
⌨️ entry/src/main/ets/utils/GlobalState.ets
import preferences from '@ohos.data.preferences';
import { UIAbilityContext } from '@ohos.abilityAccessCtrl';
// 全局状态类型
export interface GlobalState {
userInfo: any;
cartCount: number;
theme: 'light' | 'dark';
}
// 全局状态管理
export class GlobalStateManager {
private static instance: GlobalStateManager | null = null;
private dataPreferences: preferences.Preferences | null = null;
private globalState: GlobalState = { userInfo: null, cartCount: 0, theme: 'light' };
// 单例模式
static getInstance(): GlobalStateManager {
if (!GlobalStateManager.instance) {
GlobalStateManager.instance = new GlobalStateManager();
}
return GlobalStateManager.instance;
}
// 初始化全局状态
async init(context: UIAbilityContext): Promise<void> {
if (!this.dataPreferences) {
this.dataPreferences = await preferences.getPreferences(context, 'app_preferences');
}
this.loadFromPreferences();
}
// 从Preferences加载全局状态
private loadFromPreferences(): void {
this.globalState.userInfo = this.dataPreferences!.getSync('userInfo', null);
this.globalState.cartCount = this.dataPreferences!.getSync('cartCount', 0);
this.globalState.theme = this.dataPreferences!.getSync('theme', 'light');
}
// 保存全局状态到Preferences
async saveToPreferences(): Promise<void> {
await this.dataPreferences!.put('userInfo', this.globalState.userInfo);
await this.dataPreferences!.put('cartCount', this.globalState.cartCount);
await this.dataPreferences!.put('theme', this.globalState.theme);
await this.dataPreferences!.flush();
}
// 获取全局状态
getState(): GlobalState {
return this.globalState;
}
// 更新全局状态
async setState(key: keyof GlobalState, value: any): Promise<void> {
this.globalState[key] = value;
await this.saveToPreferences();
}
}
⌨️ entry/src/main/ets/components/GlobalStateComponent.ets
import { GlobalStateManager } from '../utils/GlobalState';
@Component
export struct GlobalStateComponent {
@State globalState: GlobalStateManager['getState']() = GlobalStateManager.getInstance().getState();
build() {
Column({ space: 0 }) {
// 子组件内容
BuilderContainer();
}
.width('100%')
.height('100%')
.backgroundColor(this.globalState.theme === 'light' ? '#F5F5F5' : '#121212');
}
aboutToAppear() {
// 监听全局状态变化
AppStorage.watch('globalState', () => {
this.globalState = GlobalStateManager.getInstance().getState();
});
}
}
⌨️ entry/src/main/ets/components/MyPageComponent.ets
import { GlobalStateManager } from '../utils/GlobalState';
import { RouterUtils } from '../utils/RouterUtils';
@Component
export struct MyPageComponent {
@State userInfo: any = GlobalStateManager.getInstance().getState().userInfo;
@State cartCount: number = GlobalStateManager.getInstance().getState().cartCount;
build() {
Column({ space: 0 }) {
// 用户信息
if (this.userInfo) {
Row({ space: 16 }) {
Image(this.userInfo.avatar)
.width(60)
.height(60)
.objectFit(ImageFit.Contain)
.borderRadius(30);
Column({ space: 8 }) {
Text(this.userInfo.nickname)
.fontSize(18)
.fontWeight(FontWeight.Bold)
.textColor('#000000');
Text(this.userInfo.email)
.fontSize(14)
.textColor('#666666');
}
.layoutWeight(1);
}
.width('100%')
.height('auto')
.padding(16);
}
// 我的订单
Row({ space: 16 }) {
Image($r('app.media.order'))
.width(24)
.height(24)
.objectFit(ImageFit.Contain);
Text('我的订单')
.fontSize(14)
.textColor('#000000');
Image($r('app.media.right'))
.width(24)
.height(24)
.objectFit(ImageFit.Contain);
}
.width('100%')
.height('auto')
.padding(16)
.onClick(() => {
RouterUtils.push('pages/OrderPage');
});
// 我的购物车
Row({ space: 16 }) {
Image($r('app.media.cart'))
.width(24)
.height(24)
.objectFit(ImageFit.Contain);
Text('我的购物车')
.fontSize(14)
.textColor('#000000');
if (this.cartCount > 0) {
Text(`${this.cartCount}`)
.fontSize(12)
.textColor('#FFFFFF')
.backgroundColor('#FF0000')
.width(20)
.height(20)
.textAlign(TextAlign.Center)
.borderRadius(10);
}
Image($r('app.media.right'))
.width(24)
.height(24)
.objectFit(ImageFit.Contain);
}
.width('100%')
.height('auto')
.padding(16)
.onClick(() => {
RouterUtils.push('pages/CartPage');
});
// 登录/退出按钮
if (this.userInfo) {
Button('退出登录')
.width('100%')
.height(48)
.backgroundColor('#FFFFFF')
.textColor('#007DFF')
.border({ width: 1, color: '#007DFF' })
.onClick(() => {
GlobalStateManager.getInstance().setState('userInfo', null);
});
} else {
Button('登录')
.width('100%')
.height(48)
.backgroundColor('#007DFF')
.onClick(() => {
RouterUtils.push('pages/LoginPage');
});
}
}
.width('100%')
.height('100%')
.backgroundColor('#F5F5F5');
}
aboutToAppear() {
AppStorage.watch('globalState', () => {
this.userInfo = GlobalStateManager.getInstance().getState().userInfo;
this.cartCount = GlobalStateManager.getInstance().getState().cartCount;
});
}
}
⌨️ entry/src/main/ets/pages/LoginPage.ets
import { GlobalStateManager } from '../utils/GlobalState';
import { RouterUtils } from '../utils/RouterUtils';
import { UserService, LoginRequestModel } from '../services/UserService';
@Entry
@Component
struct LoginPage {
@State username: string = '';
@State password: string = '';
@State isLoading: boolean = false;
@State errorMessage: string = '';
build() {
Column({ space: 0 }) {
// 登录表单
Column({ space: 16 }) {
Text('登录')
.fontSize(24)
.fontWeight(FontWeight.Bold)
.textColor('#000000');
Input({
text: this.username,
placeholder: '请输入用户名'
})
.width('100%')
.height(48)
.fontSize(14)
.backgroundColor('#FFFFFF')
.borderRadius(8)
.onChange((value: string) => {
this.username = value;
});
Input({
text: this.password,
placeholder: '请输入密码',
type: InputType.Password
})
.width('100%')
.height(48)
.fontSize(14)
.backgroundColor('#FFFFFF')
.borderRadius(8)
.onChange((value: string) => {
this.password = value;
});
if (this.errorMessage) {
Text(this.errorMessage)
.fontSize(14)
.textColor('#FF0000');
}
Button('登录')
.width('100%')
.height(48)
.backgroundColor('#007DFF')
.onClick(() => this.login())
.enabled(!this.isLoading);
}
.width('100%')
.height('auto')
.padding(16);
// 加载动画
if (this.isLoading) {
LoadingComponent({ isLoading: this.isLoading });
}
}
.width('100%')
.height('100%')
.backgroundColor('#F5F5F5');
}
async login() {
this.isLoading = true;
this.errorMessage = '';
try {
const request: LoginRequestModel = {
username: this.username,
password: this.password
};
const response = await UserService.getInstance().login(request);
// 更新全局状态
GlobalStateManager.getInstance().setState('userInfo', response.userInfo);
RouterUtils.pop();
} catch (error) {
this.errorMessage = error.message;
} finally {
this.isLoading = false;
}
}
}
在 entry/src/main/module.json5 中配置预加载页面:
{
"module": {
"pages": [
"pages/Index",
"pages/SearchPage",
"pages/ProductDetailPage",
"pages/CartPage",
"pages/OrderPage",
"pages/PaymentPage",
"pages/LoginPage",
"pages/MyPage"
],
"preloadPages": [
"pages/ProductDetailPage",
"pages/CartPage"
]
}
}
⌨️ entry/src/main/ets/entryability/EntryAbility.ts(修改)
import { RouterUtils } from '../utils/RouterUtils';
export default class EntryAbility extends UIAbility {
onCreate(want: Want, launchParam: AbilityConstant.LaunchParam): void {
// 初始化预加载页面
RouterUtils.preload('pages/ProductDetailPage');
RouterUtils.preload('pages/CartPage');
}
// ...
}
在 entry/src/main/ets/utils/RouterUtils.ets 中添加动画配置:
import router from '@ohos.router';
// 页面路由工具类
export class RouterUtils {
// 跳转到指定页面并添加动画
static pushWithAnimation(url: string, params?: RouterParams, animation?: RouterAnimation): void {
router.pushUrl({
url,
params,
animParams: animation || { duration: 300, curve: Curve.EaseOut, type: RouterAnimationType.Translate }
});
}
// 返回到上一页并添加动画
static popWithAnimation(animation?: RouterAnimation): void {
router.back({
animParams: animation || { duration: 300, curve: Curve.EaseOut, type: RouterAnimationType.Translate }
});
}
}
// 路由动画类型
export type RouterAnimationType = 'Translate' | 'Scale' | 'Rotate';
// 路由动画配置
export interface RouterAnimation {
duration: number;
curve: Curve;
type: RouterAnimationType;
direction?: 'Left' | 'Right' | 'Top' | 'Bottom';
}
在 entry/src/main/ets/utils/RouterUtils.ets 中添加拦截器:
import router from '@ohos.router';
import { GlobalStateManager } from '../utils/GlobalState';
// 页面路由工具类
export class RouterUtils {
// 设置路由拦截器
static setInterceptor(): void {
router.setInterceptor({
shouldRouter: (url: string): boolean => {
// 检查是否需要登录
const needLogin = ['pages/OrderPage', 'pages/PaymentPage', 'pages/MyPage'].includes(url);
if (needLogin && !GlobalStateManager.getInstance().getState().userInfo) {
RouterUtils.push('pages/LoginPage', { fromUrl: url });
return false;
}
return true;
},
beforeEach: (url: string): void => {
console.log('路由拦截器:beforeEach - ' + url);
},
afterEach: (url: string): void => {
console.log('路由拦截器:afterEach - ' + url);
}
});
}
}
⌨️ entry/src/main/ets/entryability/EntryAbility.ts(修改)
import { RouterUtils } from '../utils/RouterUtils';
export default class EntryAbility extends UIAbility {
onCreate(want: Want, launchParam: AbilityConstant.LaunchParam): void {
// 初始化路由拦截器
RouterUtils.setInterceptor();
}
// ...
}
在 entry/src/main/resources/base/media 目录下添加动画资源(如旋转动画、缩放动画)。
在 entry/src/main/module.json5 中配置预加载页面。
① 在DevEco Studio中点击「Run」按钮;
② 选择调试设备,点击「OK」;
③ 等待编译安装完成,应用会自动在设备上启动。
✅ 页面跳转:首页→商品详情→购物车→订单→支付的完整页面跳转;
✅ 路由参数传递:传递商品ID、数量、选中商品信息、订单信息;
✅ 全局状态管理:用户信息、购物车数量、主题切换;
✅ 路由拦截:未登录时拦截订单、支付、我的页面跳转,跳转到登录页面;
✅ 预加载:商品详情、购物车页面预加载,提升加载速度。
本文作为《鸿蒙APP开发从入门到精通》的第5篇,完成了:

微信公众号「极客日志」,在微信中扫描左侧二维码关注。展示文案:极客日志 zeeklog
将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online
将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online
将 Markdown(GFM)转为 HTML 片段,浏览器内 marked 解析;与 HTML转Markdown 互为补充。 在线工具,Markdown转HTML在线工具,online
将 HTML 片段转为 GitHub Flavored Markdown,支持标题、列表、链接、代码块与表格等;浏览器内处理,可链接预填。 在线工具,HTML转Markdown在线工具,online
通过删除不必要的空白来缩小和压缩JSON。 在线工具,JSON 压缩在线工具,online
将JSON字符串修饰为友好的可读格式。 在线工具,JSON美化和格式化在线工具,online