🚀uni-app 自定義路由封裝模塊詳解(附源碼逐行解讀)
📌 請收藏 + 點贊 + 關注,獲取更多 uni-app 項目實用技巧!
在實際 uni-app 項目中,我們常常需要對 uni.navigateTo
、uni.switchTab
等 API 做一層封裝,以便統一處理頁面跳轉、參數傳遞、登錄攔截等邏輯。本篇將完整展示一份功能強大的路由封裝方案,并逐行解釋其實現邏輯,幫助你構建更可控、易擴展的項目架構。
📦源碼展示(含說明性注釋)
👇以下是完整源碼,已集成:
- 頁面路徑分析
- query 和 params 分離
- 登錄攔截(導航守衛)
- 頁面跳轉封裝(支持多種跳轉模式)
- 首頁識別與返回邏輯
- 頁簽識別
- 頁面棧方法調用
- 登錄回調鉤子
📄 完整代碼如下(點擊右側小箭頭可展開逐行解讀):
點擊展開完整源碼逐行解讀// 引入 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 到 URLif (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 頁,強制使用 switchTabif (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
你可以在此基礎上繼續拓展如:權限校驗、頁面緩存、歷史記錄管理、動畫過渡管理等功能。