UNPKG

@meng-xi/uni-router

Version:

为 uni-app 提供类似 vue-router 风格的路由

451 lines (450 loc) 16.9 kB
import { unref as p, ref as g, onUnmounted as d, watch as m, nextTick as A } from "vue"; var f = /* @__PURE__ */ ((e) => (e.NAVIGATION_ABORTED = "NAVIGATION_ABORTED", e.NAVIGATION_REDIRECT = "NAVIGATION_REDIRECT", e.NAVIGATION_FAILED = "NAVIGATION_FAILED", e.INVALID_METHOD = "INVALID_METHOD", e))(f || {}); const b = ["app", "app-plus", "app-plus-nvue", "app-nvue", "app-android", "app-ios", "app-harmony"]; class c extends Error { /** * 错误类型,使用 RouterErrorType 枚举来定义具体的错误类型 * 该枚举包含导航中止、导航重定向、导航失败、无效方法等常见的路由错误类型 */ type; /** * 错误位置,标识错误发生时对应的路由位置 * 可以是字符串类型的路径,也可以是包含路径和查询参数的 RouteLocationRaw 类型对象 * 该属性为可选属性,可能不存在 */ location; /** * 构造函数,用于创建 RouterError 实例 * @param type 错误类型,使用 RouterErrorType 枚举值 * @param message 错误消息,用于描述错误的具体信息 * @param location 目标位置,标识错误发生时对应的路由位置,可选参数 */ constructor(t, n, o) { super(n), this.type = t, this.location = o, Object.setPrototypeOf(this, c.prototype); } /** * 创建导航中止错误实例 * 当导航过程被前置守卫中止时,可使用此方法创建对应的错误对象 * @returns 一个表示导航中止错误的 RouterError 实例 */ static navigationAborted() { return new c(f.NAVIGATION_ABORTED, "Navigation aborted by guard"); } /** * 创建导航重定向错误实例 * 当导航过程中发生重定向时,可使用此方法创建对应的错误对象 * @param location 重定向位置,标识重定向后的路由位置 * @returns 一个表示导航重定向错误的 RouterError 实例 */ static navigationRedirect(t) { return new c(f.NAVIGATION_REDIRECT, "Navigation redirected", t); } /** * 创建导航失败错误实例 * 当导航过程中出现异常导致失败时,可使用此方法创建对应的错误对象 * @param message 错误信息,用于描述导航失败的具体原因,若未提供则使用默认信息 * @returns 一个表示导航失败错误的 RouterError 实例 */ static navigationFailed(t) { return new c(f.NAVIGATION_FAILED, t || "Navigation failed"); } /** * 创建无效方法错误实例 * 当尝试使用无效的导航方法时,可使用此方法创建对应的错误对象 * @param method 方法名,标识尝试使用的无效导航方法 * @returns 一个表示无效方法错误的 RouterError 实例 */ static invalidMethod(t) { return new c(f.INVALID_METHOD, `Navigation method ${t} not available`); } } function l(e, t) { if (e.startsWith("tabBar/")) return e.startsWith("/") ? e : `/${e}`; const n = e.startsWith("/") ? e : `/${e}`; if (e.includes("?") || !t) return n; const o = Object.keys(t).filter((r) => t[r] !== void 0 && t[r] !== null).map((r) => `${encodeURIComponent(r)}=${encodeURIComponent(String(t[r]))}`).join("&"); return o ? `${n}?${o}` : n; } function N(e) { if (!e) return null; let t = {}; const n = String(process.env.UNI_PLATFORM).toString().toLowerCase(); return ["mp-weixin", "mp-alipay", "mp-baidu", "mp-toutiao", "mp-qq"].includes(n) && (t = e.options || {}), ["h5"].includes(n) && (t = e.$vm?.$route?.query || {}), ["app-plus"].includes(n) && (t = e.$vm?.$mp?.query || {}), { // 路由路径,若 currentPage.route 不存在则使用空字符串 path: e.route || "", // 完整路由路径,调用 buildUrl 函数构建,若 currentPage.route 不存在则使用空字符串 fullPath: l(e.route || ""), // 查询参数对象 query: t }; } function T(e) { if (typeof e == "string") return { path: e }; const t = typeof e.query == "object" && e.query !== null ? Object.fromEntries(Object.entries(e.query).map(([n, o]) => [n, String(o)])) : void 0; return { path: e.path, query: t }; } function v() { return !0; } function E(e) { throw new Error(`[uni-router error]:${e}`); } function y(e) { const t = {}; return Object.keys(e).map((n) => { t[n] = p(e[n]); }), t; } function I() { const e = String(process.env.UNI_PLATFORM).toString().toLowerCase(); return b.includes(e); } class u { /** * 单例实例,用于单例模式调用 * @private * @static * @type {Router | undefined} */ static instance; /** * 路由配置列表,存储所有的路由配置信息 * @private * @type {RouteConfig[]} */ routes; /** * 全局前置守卫列表,在每次导航前依次执行 * @private * @type {NavigationGuard[]} */ beforeEachHooks; /** * 全局后置钩子列表,在每次导航成功后依次执行 * @private * @type {AfterEachHook[]} */ afterEachHooks; /** * 存储用户自定义的 getCurrentRoute 函数 * @private * @type {(() => ReturnType<typeof getCurrentRouteUtil>) | undefined} */ customGetCurrentRoute; /** * 构造函数,初始化 Router 实例 * 私有构造函数,防止外部直接实例化,保证单例模式的实现 * @param {RouterOptions} [options={}] - 路由配置选项,包含路由配置列表等信息,默认为空对象 */ constructor(t = {}) { this.routes = t.routes || [], this.beforeEachHooks = [], this.afterEachHooks = [], this.customGetCurrentRoute = t.customGetCurrentRoute; } /** * 获取单例实例 * 首次获取实例时需要传入路由配置选项,后续获取使用之前的配置 * @static * @param {RouterOptions} [options] - 路由配置选项 * @returns {Router} Router 实例 */ static getInstance(t) { return u.instance || (u.instance = new u(t)), u.instance; } /** * 以推入新页面的方式进行路由导航 - 静态方法 * 通过单例实例调用实例方法实现路由导航 * @static * @param {RouteLocationRaw} location - 目标路由位置信息,可以是字符串路径或包含路径和查询参数的对象 * @param {RouterOpenAnimation} [animation] - 窗口显示动画配置 * @returns {Promise<void>} 一个 Promise,导航成功时 resolve,失败时 reject */ static push(t, n) { return u.getInstance().push(t, n); } /** * 以推入新页面的方式进行路由导航 - 实例方法 * @param {RouteLocationRaw} location - 目标路由位置信息,可以是字符串路径或包含路径和查询参数的对象 * @param {RouterOpenAnimation} [animation] - 窗口显示动画配置 * @returns {Promise<void>} 一个 Promise,导航成功时 resolve,失败时 reject */ push(t, n) { return this.navigate(t, "navigateTo", n); } /** * 以替换当前页面的方式进行路由导航 - 静态方法 * 通过单例实例调用实例方法实现路由导航 * @static * @param {RouteLocationRaw} location - 目标路由位置信息,可以是字符串路径或包含路径和查询参数的对象 * @returns {Promise<void>} 一个 Promise,导航成功时 resolve,失败时 reject */ static replace(t) { return u.getInstance().replace(t); } /** * 以替换当前页面的方式进行路由导航 - 实例方法 * @param {RouteLocationRaw} location - 目标路由位置信息,可以是字符串路径或包含路径和查询参数的对象 * @returns {Promise<void>} 一个 Promise,导航成功时 resolve,失败时 reject */ replace(t) { return this.navigate(t, "redirectTo"); } /** * 以重新启动应用的方式进行路由导航 - 静态方法 * 通过单例实例调用实例方法实现路由导航 * @static * @param {RouteLocationRaw} location - 目标路由位置信息,可以是字符串路径或包含路径和查询参数的对象 * @returns {Promise<void>} 一个 Promise,导航成功时 resolve,失败时 reject */ static launch(t) { return u.getInstance().launch(t); } /** * 以重新启动应用的方式进行路由导航 - 实例方法 * @param {RouteLocationRaw} location - 目标路由位置信息,可以是字符串路径或包含路径和查询参数的对象 * @returns {Promise<void>} 一个 Promise,导航成功时 resolve,失败时 reject */ launch(t) { return this.navigate(t, "reLaunch"); } /** * 切换到指定的 tab 页面 - 静态方法 * 通过单例实例调用实例方法实现路由导航 * @static * @param {RouteLocationRaw} location - 目标路由位置信息,可以是字符串路径或包含路径和查询参数的对象 * @returns {Promise<void>} 一个 Promise,导航成功时 resolve,失败时 reject */ static tab(t) { return u.getInstance().tab(t); } /** * 切换到指定的 tab 页面 - 实例方法 * @param {RouteLocationRaw} location - 目标路由位置信息,可以是字符串路径或包含路径和查询参数的对象 * @returns {Promise<void>} 一个 Promise,导航成功时 resolve,失败时 reject */ tab(t) { return this.navigate(t, "switchTab"); } /** * 返回到指定层数的上一个页面 - 静态方法 * 通过单例实例调用实例方法实现页面返回 * @static * @param {number} [delta=-1] - 要返回的页面层数,默认值为 -1,表示返回上一个页面 * @param {RouterCloseAnimation} [animation] - 窗口关闭动画配置 */ static go(t = -1, n) { u.getInstance().go(t, n); } /** * 返回到指定层数的上一个页面 - 实例方法 * 调用 uni-app 的 navigateBack 方法实现页面返回 * @param {number} [delta=-1] - 要返回的页面层数,默认值为 -1,表示返回上一个页面 * @param {RouterCloseAnimation} [animation] - 窗口关闭动画配置 */ go(t = -1, n) { const o = { delta: Math.abs(t), ...I() && n && { animationType: n.type || "pop-out", animationDuration: n.duration || 300 } }; uni.navigateBack(o); } /** * 返回到上一个页面,等同于调用 go(-1) - 静态方法 * 通过单例实例调用实例方法实现页面返回 * @static * @param {RouterCloseAnimation} [animation] - 窗口关闭动画配置 */ static back(t) { u.getInstance().back(t); } /** * 返回到上一个页面,等同于调用 go(-1) - 实例方法 * 调用 go 方法实现页面返回 * @param {RouterCloseAnimation} [animation] - 窗口关闭动画配置 */ back(t) { this.go(-1, t); } /** * 添加全局前置守卫到守卫列表中 - 静态方法 * 通过单例实例调用实例方法添加全局前置守卫 * 这些守卫会在每次导航前依次执行 * @static * @param {NavigationGuard} guard - 全局前置守卫函数 */ static beforeEach(t) { u.getInstance().beforeEach(t); } /** * 添加全局前置守卫到守卫列表中 - 实例方法 * 将全局前置守卫函数添加到前置守卫列表中 * 这些守卫会在每次导航前依次执行 * @param {NavigationGuard} guard - 全局前置守卫函数 */ beforeEach(t) { this.beforeEachHooks.push(t); } /** * 添加全局后置钩子到钩子列表中 - 静态方法 * 通过单例实例调用实例方法添加全局后置钩子 * 这些钩子会在每次导航成功后依次执行 * @static * @param {AfterEachHook} hook - 全局后置钩子函数 */ static afterEach(t) { u.getInstance().afterEach(t); } /** * 添加全局后置钩子到钩子列表中 - 实例方法 * 将全局后置钩子函数添加到后置钩子列表中 * 这些钩子会在每次导航成功后依次执行 * @param {AfterEachHook} hook - 全局后置钩子函数 */ afterEach(t) { this.afterEachHooks.push(t); } /** * 设置自定义的 getCurrentRoute 函数 * @static * @param {() => ReturnType<typeof getCurrentRouteUtil>} customFunction - 自定义的 getCurrentRoute 函数 */ static setCustomGetCurrentRoute(t) { u.getInstance().setCustomGetCurrentRoute(t); } /** * 设置自定义的 getCurrentRoute 函数 * @param {() => ReturnType<typeof getCurrentRouteUtil>} customFunction - 自定义的 getCurrentRoute 函数 */ setCustomGetCurrentRoute(t) { this.customGetCurrentRoute = t; } /** * 获取当前页面的路由信息 - 静态方法 * 通过单例实例调用实例方法获取当前页面的路由信息 * @static * @returns {ReturnType<typeof getCurrentRouteUtil> | null} 当前页面的路由信息对象,如果获取失败则返回 null */ static getCurrentRoute() { return u.getInstance().getCurrentRoute(); } /** * 获取当前页面的路由信息 - 实例方法 * 优先使用用户自定义的 getCurrentRoute 函数,若未定义则使用默认实现 * @returns {ReturnType<typeof getCurrentRouteUtil> | null} 当前页面的路由信息对象,如果获取失败则返回 null */ getCurrentRoute() { if (this.customGetCurrentRoute) return this.customGetCurrentRoute(); const t = getCurrentPages(); return t.length > 0 ? N(t[t.length - 1]) : null; } /** * 执行路由导航操作,包含前置守卫和后置钩子的处理 * @private * @param {RouteLocationRaw} location - 目标路由位置信息,可以是字符串路径或包含路径和查询参数的对象 * @param {RouterMethod} method - 导航方法,如 'navigateTo'、'redirectTo' 等 * @param {RouterOpenAnimation} [animation] - 窗口显示动画配置 * @returns {Promise<void>} 一个 Promise,导航成功时 resolve,失败时 reject */ async navigate(t, n, o) { const { path: r, query: h } = T(t), s = this.getCurrentRoute(), i = { path: r, query: h || {}, fullPath: l(r, h) }; try { for (const a of this.beforeEachHooks) await this.runGuard(a, i, s); n === "switchTab" ? await this.callMxMethod("switchTab", l(r)) : await this.callMxMethod(n, i.fullPath, o); for (const a of this.afterEachHooks) a(i, s); } catch (a) { if (a instanceof c && a.type === f.NAVIGATION_REDIRECT && a.location) return this.push(a.location); throw c.navigationFailed(a instanceof Error ? a.message : String(a)); } } /** * 执行单个全局前置守卫函数 * @private * @param {NavigationGuard} guard - 全局前置守卫函数 * @param {any} to - 目标路由信息对象 * @param {any} from - 当前路由信息对象 * @returns {Promise<void>} 一个 Promise,守卫验证通过时 resolve,验证失败时 reject */ runGuard(t, n, o) { return new Promise((r, h) => { const s = (i) => { i === !1 ? h(c.navigationAborted()) : typeof i == "string" || typeof i == "object" && i !== null ? h(c.navigationRedirect(i)) : r(); }; try { const i = t(n, o, s); i !== void 0 && Promise.resolve(i).then((a) => { a === !1 ? h(c.navigationAborted()) : typeof a == "string" || typeof a == "object" && a !== null ? h(c.navigationRedirect(a)) : r(); }).catch(h); } catch (i) { h(i); } }); } /** * 调用 uni-app 的路由方法进行导航 * @private * @param {RouterMethod} method - 导航方法,如 'navigateTo'、'redirectTo' 等 * @param {string} url - 导航的目标 URL * @param {RouterOpenAnimation} [animation] - 窗口显示动画配置 * @returns {Promise<void>} 一个 Promise,导航成功时 resolve,失败时 reject */ callMxMethod(t, n, o) { return new Promise((r, h) => { const s = uni[t], i = { url: n }; I() && o && (i.animationType = o.type || "pop-in", i.animationDuration = o.duration || 300), typeof s == "function" ? s(i).then(r).catch((a) => h(c.navigationFailed(a instanceof Error ? a.message : String(a)))) : h(c.invalidMethod(t)); }); } } function C(e) { const t = g(null), n = g(!1); async function o() { const s = p(t); return s || E("The router instance has not been obtained, please make sure that the router has been rendered when performing the router operation!"), await A(), s; } function r(s) { d(() => { t.value = null, n.value = null; }), !(p(n) && v() && s === p(t)) && (t.value = s, n.value = !0, m( () => e, () => { e && s.setProps(y(e)); }, { immediate: !0, deep: !0 } )); } return [r, { /** * 异步设置 Router 组件的属性 * * @param routerProps - 要设置的 Router 组件属性 */ setProps: async (s) => { (await o()).setProps(s); } }]; } export { u as Router, l as buildUrl, N as getCurrentRoute, T as parseLocation, C as useMxRouter };