363 lines
6.8 KiB
TypeScript
363 lines
6.8 KiB
TypeScript
import { PAGES, TABS } from "../ctx";
|
||
import type { BackOptions, PageInstance, PushOptions } from "../types";
|
||
import {
|
||
storage,
|
||
last,
|
||
isNull,
|
||
isEmpty,
|
||
get,
|
||
isFunction,
|
||
toArray,
|
||
map,
|
||
debounce,
|
||
nth,
|
||
assign,
|
||
parse
|
||
} from "../utils";
|
||
|
||
// 路由信息类型
|
||
type RouteInfo = {
|
||
path: string;
|
||
query: UTSJSONObject;
|
||
meta: UTSJSONObject;
|
||
isAuth?: boolean;
|
||
};
|
||
|
||
// 跳转前钩子类型
|
||
type BeforeEach = (to: RouteInfo, from: PageInstance, next: () => void) => void;
|
||
// 登录后回调类型
|
||
type AfterLogin = () => void;
|
||
|
||
// 路由事件集合
|
||
type Events = {
|
||
beforeEach?: BeforeEach;
|
||
afterLogin?: AfterLogin;
|
||
};
|
||
|
||
// 路由核心类
|
||
export class Router {
|
||
private eventsMap = {} as Events; // 事件存储
|
||
|
||
// 获取传递的 params 参数
|
||
params() {
|
||
return (storage.get("router-params") ?? {}) as UTSJSONObject;
|
||
}
|
||
|
||
// 获取传递的 query 参数
|
||
query() {
|
||
return this.route()?.query ?? {};
|
||
}
|
||
|
||
// 获取默认路径,支持 home 和 login
|
||
defaultPath(name: "home" | "login") {
|
||
const paths = {
|
||
home: PAGES[0].path, // 首页为第一个页面
|
||
login: "/pages/user/login"
|
||
};
|
||
|
||
return get(paths, name) as string;
|
||
}
|
||
|
||
// 获取当前页面栈的所有页面实例
|
||
getPages(): PageInstance[] {
|
||
return map(getCurrentPages(), (e) => {
|
||
let path = e.route!;
|
||
|
||
// 根路径自动转为首页
|
||
if (path == "/") {
|
||
path = this.defaultPath("home");
|
||
}
|
||
|
||
// 补全路径前缀
|
||
if (!path.startsWith("/")) {
|
||
path = "/" + path;
|
||
}
|
||
|
||
// 获取页面样式
|
||
const page = PAGES.find((e) => e.path == path);
|
||
const style = page?.style;
|
||
const meta = page?.meta;
|
||
|
||
// 获取页面暴露的方法
|
||
// @ts-ignore
|
||
const vm = e.vm as any;
|
||
|
||
let exposed = vm;
|
||
|
||
// #ifdef H5
|
||
exposed = get(e, "vm.$.exposed");
|
||
// #endif
|
||
|
||
// 获取页面 query 参数
|
||
// @ts-ignore
|
||
const query = e.options;
|
||
|
||
return {
|
||
path,
|
||
vm,
|
||
exposed,
|
||
style,
|
||
meta,
|
||
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,
|
||
events,
|
||
isAuth
|
||
} = 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;
|
||
}
|
||
};
|
||
|
||
// 跳转前钩子处理
|
||
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);
|
||
} else {
|
||
next();
|
||
}
|
||
}
|
||
|
||
// 回到首页
|
||
home() {
|
||
this.push({
|
||
path: this.defaultPath("home")
|
||
});
|
||
}
|
||
|
||
// 返回上一页
|
||
back(options: BackOptions | null = null) {
|
||
if (this.isFirstPage()) {
|
||
this.home();
|
||
} else {
|
||
const delta = options?.delta ?? 1;
|
||
|
||
// 执行跳转函数
|
||
const next = () => {
|
||
uni.navigateBack({ ...(options ?? {}) });
|
||
};
|
||
|
||
// 跳转前钩子处理
|
||
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();
|
||
}
|
||
}
|
||
}
|
||
|
||
// 获取页面元数据
|
||
getMeta(path: string) {
|
||
return PAGES.find((e) => path != null && typeof path === 'string' && path.includes(e.path))?.meta ?? ({} as UTSJSONObject);
|
||
}
|
||
|
||
// 执行当前页面暴露的方法
|
||
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
|
||
});
|
||
}
|
||
// 登录后回调
|
||
if (this.eventsMap.afterLogin != null) {
|
||
this.eventsMap.afterLogin!();
|
||
}
|
||
// 触发全局 afterLogin 事件
|
||
uni.$emit("afterLogin");
|
||
}
|
||
|
||
// 注册跳转前钩子
|
||
beforeEach(cb: BeforeEach) {
|
||
this.eventsMap.beforeEach = cb;
|
||
}
|
||
|
||
// 注册登录后回调
|
||
afterLogin(cb: AfterLogin) {
|
||
this.eventsMap.afterLogin = cb;
|
||
}
|
||
}
|
||
|
||
// 单例导出
|
||
export const router = new Router();
|