@meng-xi/uni-router
Version:
为 uni-app 提供类似 vue-router 风格的路由
451 lines (450 loc) • 16.9 kB
JavaScript
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
};