uni-app 自定义路由封装模块详解(附源码逐行解读)

🚀uni-app 自定义路由封装模块详解(附源码逐行解读)

📌 请收藏 + 点赞 + 关注,获取更多 uni-app 项目实用技巧!

在实际 uni-app 项目中,我们常常需要对 uni.navigateTouni.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

你可以在此基础上继续拓展如:权限校验、页面缓存、历史记录管理、动画过渡管理等功能。

相关推荐
Angel_girl31923 分钟前
vue项目使用svg图标
前端·vue.js
難釋懷28 分钟前
vue 项目中常用的 2 个 Ajax 库
前端·vue.js·ajax
Qian Xiaoo29 分钟前
Ajax入门
前端·ajax·okhttp
爱生活的苏苏1 小时前
vue生成二维码图片+文字说明
前端·vue.js
拉不动的猪1 小时前
安卓和ios小程序开发中的兼容性问题举例
前端·javascript·面试
炫彩@之星1 小时前
Chrome书签的导出与导入:步骤图
前端·chrome
贩卖纯净水.1 小时前
浏览器兼容-polyfill-本地服务-优化
开发语言·前端·javascript
前端百草阁1 小时前
从npm库 Vue 组件到独立SDK:打包与 CDN 引入的最佳实践
前端·vue.js·npm
夏日米米茶1 小时前
Windows系统下npm报错node-gyp configure got “gyp ERR“解决方法
前端·windows·npm
且白2 小时前
vsCode使用本地低版本node启动配置文件
前端·vue.js·vscode·编辑器