2025-07-21 16:47:04 +08:00
|
|
|
|
import { PAGES, TABS } from "../ctx";
|
|
|
|
|
|
import type { BackOptions, PageInstance, PushOptions } from "../types";
|
2025-09-23 15:59:19 +08:00
|
|
|
|
import {
|
|
|
|
|
|
storage,
|
|
|
|
|
|
last,
|
|
|
|
|
|
isNull,
|
|
|
|
|
|
isEmpty,
|
|
|
|
|
|
get,
|
|
|
|
|
|
isFunction,
|
|
|
|
|
|
toArray,
|
|
|
|
|
|
map,
|
|
|
|
|
|
debounce,
|
2025-10-28 11:31:49 +08:00
|
|
|
|
nth,
|
|
|
|
|
|
assign,
|
|
|
|
|
|
parse
|
2025-09-23 15:59:19 +08:00
|
|
|
|
} from "../utils";
|
2025-07-21 16:47:04 +08:00
|
|
|
|
|
|
|
|
|
|
// 路由信息类型
|
|
|
|
|
|
type RouteInfo = {
|
|
|
|
|
|
path: string;
|
2025-09-23 15:59:19 +08:00
|
|
|
|
query: UTSJSONObject;
|
|
|
|
|
|
meta: UTSJSONObject;
|
|
|
|
|
|
isAuth?: boolean;
|
2025-07-21 16:47:04 +08:00
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// 跳转前钩子类型
|
2025-09-22 18:08:46 +08:00
|
|
|
|
type BeforeEach = (to: RouteInfo, from: PageInstance, next: () => void) => void;
|
2025-07-21 16:47:04 +08:00
|
|
|
|
// 登录后回调类型
|
|
|
|
|
|
type AfterLogin = () => void;
|
|
|
|
|
|
|
|
|
|
|
|
// 路由事件集合
|
|
|
|
|
|
type Events = {
|
|
|
|
|
|
beforeEach?: BeforeEach;
|
|
|
|
|
|
afterLogin?: AfterLogin;
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// 路由核心类
|
|
|
|
|
|
export class Router {
|
2025-09-23 15:59:19 +08:00
|
|
|
|
private eventsMap = {} as Events; // 事件存储
|
2025-07-21 16:47:04 +08:00
|
|
|
|
|
2025-10-28 11:31:49 +08:00
|
|
|
|
// 获取传递的 params 参数
|
2025-07-21 16:47:04 +08:00
|
|
|
|
params() {
|
2025-09-23 15:59:19 +08:00
|
|
|
|
return (storage.get("router-params") ?? {}) as UTSJSONObject;
|
2025-07-21 16:47:04 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-10-28 11:31:49 +08:00
|
|
|
|
// 获取传递的 query 参数
|
|
|
|
|
|
query() {
|
|
|
|
|
|
return this.route()?.query ?? {};
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-07-21 16:47:04 +08:00
|
|
|
|
// 获取默认路径,支持 home 和 login
|
|
|
|
|
|
defaultPath(name: "home" | "login") {
|
|
|
|
|
|
const paths = {
|
|
|
|
|
|
home: PAGES[0].path, // 首页为第一个页面
|
|
|
|
|
|
login: "/pages/user/login"
|
|
|
|
|
|
};
|
2025-09-23 15:59:19 +08:00
|
|
|
|
|
2025-07-21 16:47:04 +08:00
|
|
|
|
return get(paths, name) as string;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 获取当前页面栈的所有页面实例
|
|
|
|
|
|
getPages(): PageInstance[] {
|
|
|
|
|
|
return map(getCurrentPages(), (e) => {
|
|
|
|
|
|
let path = e.route!;
|
2025-09-23 15:59:19 +08:00
|
|
|
|
|
2025-07-21 16:47:04 +08:00
|
|
|
|
// 根路径自动转为首页
|
|
|
|
|
|
if (path == "/") {
|
|
|
|
|
|
path = this.defaultPath("home");
|
|
|
|
|
|
}
|
2025-09-23 15:59:19 +08:00
|
|
|
|
|
2025-07-21 16:47:04 +08:00
|
|
|
|
// 补全路径前缀
|
|
|
|
|
|
if (!path.startsWith("/")) {
|
|
|
|
|
|
path = "/" + path;
|
|
|
|
|
|
}
|
2025-09-23 15:59:19 +08:00
|
|
|
|
|
2025-07-21 16:47:04 +08:00
|
|
|
|
// 获取页面样式
|
2025-09-22 18:08:46 +08:00
|
|
|
|
const page = PAGES.find((e) => e.path == path);
|
2025-09-23 13:22:09 +08:00
|
|
|
|
const style = page?.style;
|
|
|
|
|
|
const meta = page?.meta;
|
2025-09-23 15:59:19 +08:00
|
|
|
|
|
2025-07-21 16:47:04 +08:00
|
|
|
|
// 获取页面暴露的方法
|
|
|
|
|
|
// @ts-ignore
|
2025-09-23 15:59:19 +08:00
|
|
|
|
const vm = e.vm as any;
|
|
|
|
|
|
|
|
|
|
|
|
let exposed = vm;
|
|
|
|
|
|
|
2025-07-21 16:47:04 +08:00
|
|
|
|
// #ifdef H5
|
|
|
|
|
|
exposed = get(e, "vm.$.exposed");
|
|
|
|
|
|
// #endif
|
2025-09-23 15:59:19 +08:00
|
|
|
|
|
2025-07-21 16:47:04 +08:00
|
|
|
|
// 获取页面 query 参数
|
2025-10-28 11:31:49 +08:00
|
|
|
|
// @ts-ignore
|
|
|
|
|
|
const query = e.options;
|
2025-09-23 15:59:19 +08:00
|
|
|
|
|
2025-07-21 16:47:04 +08:00
|
|
|
|
return {
|
|
|
|
|
|
path,
|
2025-09-23 15:59:19 +08:00
|
|
|
|
vm,
|
2025-07-21 16:47:04 +08:00
|
|
|
|
exposed,
|
|
|
|
|
|
style,
|
2025-09-22 18:08:46 +08:00
|
|
|
|
meta,
|
2025-07-21 16:47:04 +08:00
|
|
|
|
query,
|
|
|
|
|
|
isCustomNavbar: style?.navigationStyle == "custom"
|
|
|
|
|
|
} as PageInstance;
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 获取指定路径的页面实例
|
|
|
|
|
|
getPage(path: string) {
|
|
|
|
|
|
return this.getPages().find((e) => e.path == path);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 获取当前路由页面实例
|
|
|
|
|
|
route() {
|
|
|
|
|
|
return last(this.getPages());
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 获取当前页面路径
|
|
|
|
|
|
path() {
|
|
|
|
|
|
return this.route()?.path ?? "";
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 简单跳转页面(默认 navigateTo)
|
|
|
|
|
|
to(path: string) {
|
|
|
|
|
|
this.push({
|
|
|
|
|
|
path
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 路由跳转,支持多种模式和参数
|
|
|
|
|
|
push(options: PushOptions) {
|
|
|
|
|
|
let {
|
|
|
|
|
|
query = {},
|
|
|
|
|
|
params = {},
|
|
|
|
|
|
mode = "navigateTo",
|
|
|
|
|
|
path,
|
|
|
|
|
|
success,
|
|
|
|
|
|
fail,
|
|
|
|
|
|
complete,
|
|
|
|
|
|
animationType,
|
|
|
|
|
|
animationDuration,
|
2025-09-23 15:59:19 +08:00
|
|
|
|
events,
|
|
|
|
|
|
isAuth
|
2025-07-21 16:47:04 +08:00
|
|
|
|
} = options;
|
|
|
|
|
|
|
|
|
|
|
|
// 拼接 query 参数到 url
|
|
|
|
|
|
if (!isEmpty(query)) {
|
|
|
|
|
|
const arr = toArray(query, (v, k) => {
|
|
|
|
|
|
return `${k}=${v}`;
|
|
|
|
|
|
});
|
|
|
|
|
|
path += "?" + arr.join("&");
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// params 通过 storage 临时存储
|
|
|
|
|
|
if (!isEmpty(params)) {
|
|
|
|
|
|
storage.set("router-params", params, 0);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// tabBar 页面强制使用 switchTab 跳转
|
|
|
|
|
|
if (this.isTabPage(path)) {
|
|
|
|
|
|
mode = "switchTab";
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 跳转执行函数
|
|
|
|
|
|
const next = () => {
|
|
|
|
|
|
switch (mode) {
|
|
|
|
|
|
case "navigateTo":
|
|
|
|
|
|
uni.navigateTo({
|
|
|
|
|
|
url: path,
|
|
|
|
|
|
success,
|
|
|
|
|
|
events,
|
|
|
|
|
|
fail,
|
|
|
|
|
|
complete,
|
|
|
|
|
|
animationType,
|
|
|
|
|
|
animationDuration
|
|
|
|
|
|
});
|
|
|
|
|
|
break;
|
|
|
|
|
|
case "redirectTo":
|
|
|
|
|
|
uni.redirectTo({
|
|
|
|
|
|
url: path,
|
|
|
|
|
|
success,
|
|
|
|
|
|
fail,
|
|
|
|
|
|
complete
|
|
|
|
|
|
});
|
|
|
|
|
|
break;
|
|
|
|
|
|
case "reLaunch":
|
|
|
|
|
|
uni.reLaunch({
|
|
|
|
|
|
url: path,
|
|
|
|
|
|
success,
|
|
|
|
|
|
fail,
|
|
|
|
|
|
complete
|
|
|
|
|
|
});
|
|
|
|
|
|
break;
|
|
|
|
|
|
case "switchTab":
|
|
|
|
|
|
uni.switchTab({
|
|
|
|
|
|
url: path,
|
|
|
|
|
|
success,
|
|
|
|
|
|
fail,
|
|
|
|
|
|
complete
|
|
|
|
|
|
});
|
|
|
|
|
|
break;
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// 跳转前钩子处理
|
2025-09-23 15:59:19 +08:00
|
|
|
|
if (this.eventsMap.beforeEach != null) {
|
|
|
|
|
|
// 当前页
|
|
|
|
|
|
const from = last(this.getPages());
|
|
|
|
|
|
|
|
|
|
|
|
// 跳转页
|
|
|
|
|
|
const to = { path, meta: this.getMeta(path), query, isAuth } as RouteInfo;
|
|
|
|
|
|
|
|
|
|
|
|
// 调用跳转前钩子
|
|
|
|
|
|
this.eventsMap.beforeEach(to, from!, next);
|
2025-07-21 16:47:04 +08:00
|
|
|
|
} else {
|
|
|
|
|
|
next();
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 回到首页
|
|
|
|
|
|
home() {
|
|
|
|
|
|
this.push({
|
|
|
|
|
|
path: this.defaultPath("home")
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-09-23 15:59:19 +08:00
|
|
|
|
// 返回上一页
|
2025-07-21 16:47:04 +08:00
|
|
|
|
back(options: BackOptions | null = null) {
|
2025-09-23 15:59:19 +08:00
|
|
|
|
if (this.isFirstPage()) {
|
|
|
|
|
|
this.home();
|
|
|
|
|
|
} else {
|
|
|
|
|
|
const delta = options?.delta ?? 1;
|
|
|
|
|
|
|
|
|
|
|
|
// 执行跳转函数
|
|
|
|
|
|
const next = () => {
|
2025-09-22 18:08:46 +08:00
|
|
|
|
uni.navigateBack({ ...(options ?? {}) });
|
2025-09-23 15:59:19 +08:00
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// 跳转前钩子处理
|
|
|
|
|
|
if (this.eventsMap.beforeEach != null) {
|
|
|
|
|
|
// 当前页
|
|
|
|
|
|
const from = last(this.getPages());
|
|
|
|
|
|
|
|
|
|
|
|
// 上一页
|
|
|
|
|
|
const to = nth(this.getPages(), -delta - 1);
|
|
|
|
|
|
|
|
|
|
|
|
if (to != null) {
|
|
|
|
|
|
// 调用跳转前钩子
|
|
|
|
|
|
this.eventsMap.beforeEach(
|
|
|
|
|
|
{
|
|
|
|
|
|
path: to.path,
|
|
|
|
|
|
query: to.query,
|
|
|
|
|
|
meta: to.meta ?? ({} as UTSJSONObject)
|
|
|
|
|
|
},
|
|
|
|
|
|
from!,
|
|
|
|
|
|
next
|
|
|
|
|
|
);
|
|
|
|
|
|
} else {
|
|
|
|
|
|
console.error("[router] found to page is null");
|
|
|
|
|
|
}
|
|
|
|
|
|
} else {
|
|
|
|
|
|
next();
|
2025-09-22 18:08:46 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
2025-09-23 15:59:19 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 获取页面元数据
|
|
|
|
|
|
getMeta(path: string) {
|
2026-01-21 01:37:34 +08:00
|
|
|
|
return PAGES.find((e) => path != null && typeof path === 'string' && path.includes(e.path))?.meta ?? ({} as UTSJSONObject);
|
2025-07-21 16:47:04 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 执行当前页面暴露的方法
|
|
|
|
|
|
callMethod(name: string, data?: any): any | null {
|
|
|
|
|
|
const fn = get(this.route()!, `$vm.$.exposed.${name}`) as (d?: any) => any | null;
|
|
|
|
|
|
if (isFunction(fn)) {
|
|
|
|
|
|
return fn(data);
|
|
|
|
|
|
}
|
|
|
|
|
|
return null;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 判断页面栈是否只有一个页面
|
|
|
|
|
|
isFirstPage() {
|
|
|
|
|
|
return getCurrentPages().length == 1;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 判断是否为首页
|
|
|
|
|
|
isHomePage() {
|
|
|
|
|
|
return this.path() == this.defaultPath("home");
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 判断是否为自定义导航栏页面
|
|
|
|
|
|
isCustomNavbarPage() {
|
|
|
|
|
|
return this.route()?.isCustomNavbar ?? false;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 判断是否为当前页面
|
|
|
|
|
|
isCurrentPage(path: string) {
|
|
|
|
|
|
return this.path() == path;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 判断是否为 tab 页面
|
|
|
|
|
|
isTabPage(path: string | null = null) {
|
|
|
|
|
|
if (path == null) {
|
|
|
|
|
|
path = this.path();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (path == "/") {
|
|
|
|
|
|
path = this.defaultPath("home");
|
|
|
|
|
|
}
|
|
|
|
|
|
return !isNull(TABS.find((e) => path == e.pagePath));
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 判断是否为登录页
|
|
|
|
|
|
isLoginPage(path: string) {
|
|
|
|
|
|
return path == this.defaultPath("login");
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 跳转到登录页(防抖处理)
|
|
|
|
|
|
login = debounce(() => {
|
|
|
|
|
|
if (!this.isLoginPage(this.path())) {
|
|
|
|
|
|
this.push({
|
|
|
|
|
|
path: "/pages/user/login",
|
|
|
|
|
|
mode: "reLaunch"
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
}, 300);
|
|
|
|
|
|
|
|
|
|
|
|
// 登录成功后跳转逻辑
|
|
|
|
|
|
nextLogin() {
|
|
|
|
|
|
const pages = this.getPages();
|
|
|
|
|
|
|
|
|
|
|
|
// 找到登录页的索引
|
|
|
|
|
|
const index = pages.findIndex((e) => this.defaultPath("login").includes(e.path));
|
|
|
|
|
|
|
|
|
|
|
|
// 未找到,则跳回首页
|
|
|
|
|
|
if (index < 0) {
|
|
|
|
|
|
this.home();
|
|
|
|
|
|
} else {
|
|
|
|
|
|
this.back({
|
|
|
|
|
|
delta: pages.length - index
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
// 登录后回调
|
2025-09-23 15:59:19 +08:00
|
|
|
|
if (this.eventsMap.afterLogin != null) {
|
|
|
|
|
|
this.eventsMap.afterLogin!();
|
2025-07-21 16:47:04 +08:00
|
|
|
|
}
|
|
|
|
|
|
// 触发全局 afterLogin 事件
|
|
|
|
|
|
uni.$emit("afterLogin");
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 注册跳转前钩子
|
2025-09-23 15:59:19 +08:00
|
|
|
|
beforeEach(cb: BeforeEach) {
|
|
|
|
|
|
this.eventsMap.beforeEach = cb;
|
2025-07-21 16:47:04 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 注册登录后回调
|
2025-09-23 15:59:19 +08:00
|
|
|
|
afterLogin(cb: AfterLogin) {
|
|
|
|
|
|
this.eventsMap.afterLogin = cb;
|
2025-07-21 16:47:04 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 单例导出
|
|
|
|
|
|
export const router = new Router();
|