🚀uni-app 自定义路由封装模块详解(附源码逐行解读)
📌 请收藏 + 点赞 + 关注,获取更多 uni-app 项目实用技巧!
在实际 uni-app 项目中,我们常常需要对 uni.navigateTo
、uni.switchTab
等 API 做一层封装,以便统一处理页面跳转、参数传递、登录拦截等逻辑。本篇将完整展示一份功能强大的路由封装方案,并逐行解释其实现逻辑,帮助你构建更可控、易扩展的项目架构。
📦源码展示(含说明性注释)
👇以下是完整源码,已集成:
- 页面路径分析
- query 和 params 分离
- 登录拦截(导航守卫)
- 页面跳转封装(支持多种跳转模式)
- 首页识别与返回逻辑
- 页签识别
- 页面栈方法调用
- 登录回调钩子
📄 完整代码如下(点击右侧小箭头可展开逐行解读):
typescript
// 引入 lodash 的 last 方法,返回数组最后一个元素
import { last } from "lodash-es";
// 引入 ctx 插件中编译期注入的页面配置、tabBar、subPackages 等
import { ctx } from "virtual:ctx";
// 项目中封装的 localStorage 工具
import { storage } from "../utils";
// 引入全局 config 配置
import { config } from "../../config";
type PushOptions = string | {
path: string; // 路径
mode?: "navigateTo" | "redirectTo" | "reLaunch" | "switchTab" | "preloadPage"; // 跳转方式
events?: { [key: string]: (data: any) => void }; // 页面间事件通信
query?: { [key: string]: any }; // URL 参数
params?: { [key: string]: any }; // 缓存参数
isGuard?: boolean; // 是否启用导航守卫
[key: string]: any;
};
type Tabs = {
text?: string;
pagePath: string;
iconPath?: string;
selectedIconPath?: string;
[key: string]: any;
}[];
// 获取所有页面配置
const routes = [...ctx.pages];
// 处理子包中的页面路径
if (ctx.subPackages) {
ctx.subPackages.forEach((a) => {
a.pages.forEach((b) => {
routes.push({
...b,
path: a.root + "/" + b.path,
});
});
});
}
// 注册钩子函数
const fn: { [key: string]: (...args: any[]) => any } = {};
// 路由核心对象
const router = {
// 读取 tabBar 配置
get tabs(): Tabs {
if (ctx.tabBar) {
return ctx.tabBar.list || [];
} else {
return [];
}
},
// 全局样式
globalStyle: ctx.globalStyle,
// 所有路由
routes,
// 当前页面 URL query 参数
get query() {
const info = this.info();
return { ...info?.query };
},
// 非 URL 参数,通过缓存传递
get params() {
return storage.get("router-params") || {};
},
// 页面路径配置
get pages() {
return {
home: "/" + (ctx.tabBar ? this.tabs[0].pagePath : ctx.pages[0].path),
...config.app.pages,
};
},
// 当前页面信息对象
currentPage() {
return last(getCurrentPages())!;
},
// 当前路径
get path() {
return router.info()?.path;
},
// 当前页面完整信息
info() {
const page = last(getCurrentPages());
if (page) {
const { route, $page, $vm, $getAppWebview }: any = page;
const q: any = {};
// 解析 query 参数
try {
$page?.fullPath
.split("?")[1]
.split("&")
.forEach((e: string) => {
const [k, v] = e.split("=");
q[k] = decodeURIComponent(v);
});
} catch (e) {}
const style = this.routes.find((e) => e.path == route)?.style;
return {
$vm,
$getAppWebview,
path: `/${route}`,
fullPath: $page?.fullPath,
query: q || {},
isTab: this.isTab(route),
style,
isCustomNavbar: style?.navigationStyle == "custom",
};
}
return null;
},
// 页面跳转主函数
push(options: PushOptions) {
if (typeof options === "string") {
options = { path: options, mode: "navigateTo" };
}
let {
path,
mode = "navigateTo",
animationType,
animationDuration,
events,
success,
fail,
complete,
query,
params,
isGuard = true,
} = options;
// 拼接 query 到 URL
if (query) {
let arr = [];
for (let i in query) {
if (query[i] !== undefined) arr.push(`${i}=${query[i]}`);
}
path += "?" + arr.join("&");
}
// 缓存传参
if (params) {
storage.set("router-params", params);
}
const data = {
url: path,
animationType,
animationDuration,
events,
success,
fail,
complete,
};
// 如果目标是 tab 页,强制使用 switchTab
if (this.isTab(path)) {
mode = "switchTab";
}
const next = () => {
switch (mode) {
case "navigateTo":
uni.navigateTo(data); break;
case "redirectTo":
uni.redirectTo(data); break;
case "reLaunch":
uni.reLaunch(data); break;
case "switchTab":
uni.switchTab(data); break;
case "preloadPage":
uni.preloadPage(data); break;
}
};
// 启用导航守卫
if (fn.beforeEach && isGuard) {
fn.beforeEach({ path: options.path, query }, next, (opt) => this.push(opt));
} else {
next();
}
},
// 返回上一页或首页
back(options?: UniApp.NavigateBackOptions) {
if (this.isFirstPage()) {
this.home();
} else {
uni.navigateBack(options || {});
}
},
// 执行当前页面某个方法
callMethod(name: string, data?: any) {
const { $vm } = this.info()!;
if ($vm && $vm.$.exposed?.[name]) {
return $vm.$.exposed[name](data);
}
},
// 是否第一页(判断是否需要返回首页)
isFirstPage() {
return getCurrentPages().length == 1;
},
// 是否是当前路径
isCurrentPage(path: string) {
return this.info()?.path === path;
},
// 返回首页
home() {
this.push(this.pages.home);
},
// 跳转 Tab 页
switchTab(name: string) {
const item = this.tabs.find((e) => e.pagePath.includes(name));
if (item) {
this.push({
path: `/${item.pagePath}`,
mode: "switchTab",
});
} else {
console.error("Not found tab", name);
}
},
// 是否是 Tab 页
isTab(path: string) {
return !!this.tabs.find((e) => path === `/${e.pagePath}`);
},
// 跳转登录页(支持 reLaunch)
login(options?: { reLaunch: boolean }) {
const { reLaunch = false } = options || {};
this.push({
path: this.pages.login,
mode: reLaunch ? "reLaunch" : "navigateTo",
isGuard: false,
});
},
// 登录成功后的回调处理
nextLogin(type?: string) {
const pages = getCurrentPages();
const index = pages.findIndex((e) => this.pages.login.includes(e.route!));
if (index <= 0) {
this.home();
} else {
this.back({ delta: pages.length - index });
}
storage.set("loginType", type);
if (fn.afterLogin) fn.afterLogin();
uni.$emit("afterLogin", { type });
},
// 注册路由钩子函数(beforeEach)
beforeEach(callback: (to: any, next: () => void, reject: (opt: PushOptions) => void) => void) {
fn.beforeEach = callback;
},
// 登录后执行回调
afterLogin(callback: () => void) {
fn.afterLogin = callback;
},
};
export { router };
✍️ 核心功能说明(重点功能归纳)
功能模块 | 描述说明 |
---|---|
router.push() |
支持全模式页面跳转,封装 query/params,支持守卫 |
router.info() |
获取当前页面详细信息 |
router.callMethod() |
跨组件执行 exposed 方法 |
router.isTab() |
判断路径是否为 Tab 页 |
router.beforeEach() |
注册跳转拦截器 |
router.nextLogin() |
登录回调重定向功能 |
router.pages |
自动生成首页路径与配置路径 |
✅总结
该路由封装模块适用于 uni-app 项目中需要进行页面跳转逻辑统一管理的场景,具备:
- 💡 统一跳转 API:支持 navigateTo、switchTab、reLaunch 等
- 🔒 导航守卫机制:登录拦截与后置回调
- 🔄 query/params 分离处理
- 🧩 模块化配置,支持挂载 ctx
你可以在此基础上继续拓展如:权限校验、页面缓存、历史记录管理、动画过渡管理等功能。