index.ts 6.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354
  1. import { PAGES, TABS } from "../ctx";
  2. import type { BackOptions, PageInstance, PushOptions } from "../types";
  3. import {
  4. storage,
  5. last,
  6. isNull,
  7. isEmpty,
  8. get,
  9. isFunction,
  10. toArray,
  11. map,
  12. debounce,
  13. nth
  14. } from "../utils";
  15. // 路由信息类型
  16. type RouteInfo = {
  17. path: string;
  18. query: UTSJSONObject;
  19. meta: UTSJSONObject;
  20. isAuth?: boolean;
  21. };
  22. // 跳转前钩子类型
  23. type BeforeEach = (to: RouteInfo, from: PageInstance, next: () => void) => void;
  24. // 登录后回调类型
  25. type AfterLogin = () => void;
  26. // 路由事件集合
  27. type Events = {
  28. beforeEach?: BeforeEach;
  29. afterLogin?: AfterLogin;
  30. };
  31. // 路由核心类
  32. export class Router {
  33. private eventsMap = {} as Events; // 事件存储
  34. // 获取缓存的路由参数
  35. params() {
  36. return (storage.get("router-params") ?? {}) as UTSJSONObject;
  37. }
  38. // 获取默认路径,支持 home 和 login
  39. defaultPath(name: "home" | "login") {
  40. const paths = {
  41. home: PAGES[0].path, // 首页为第一个页面
  42. login: "/pages/user/login"
  43. };
  44. return get(paths, name) as string;
  45. }
  46. // 获取当前页面栈的所有页面实例
  47. getPages(): PageInstance[] {
  48. return map(getCurrentPages(), (e) => {
  49. let path = e.route!;
  50. // 根路径自动转为首页
  51. if (path == "/") {
  52. path = this.defaultPath("home");
  53. }
  54. // 补全路径前缀
  55. if (!path.startsWith("/")) {
  56. path = "/" + path;
  57. }
  58. // 获取页面样式
  59. const page = PAGES.find((e) => e.path == path);
  60. const style = page?.style;
  61. const meta = page?.meta;
  62. // 获取页面暴露的方法
  63. // @ts-ignore
  64. const vm = e.vm as any;
  65. let exposed = vm;
  66. // #ifdef H5
  67. exposed = get(e, "vm.$.exposed");
  68. // #endif
  69. // 获取页面 query 参数
  70. const query = (get(e, "options") ?? {}) as UTSJSONObject;
  71. return {
  72. path,
  73. vm,
  74. exposed,
  75. style,
  76. meta,
  77. query,
  78. isCustomNavbar: style?.navigationStyle == "custom"
  79. } as PageInstance;
  80. });
  81. }
  82. // 获取指定路径的页面实例
  83. getPage(path: string) {
  84. return this.getPages().find((e) => e.path == path);
  85. }
  86. // 获取当前路由页面实例
  87. route() {
  88. return last(this.getPages());
  89. }
  90. // 获取当前页面路径
  91. path() {
  92. return this.route()?.path ?? "";
  93. }
  94. // 简单跳转页面(默认 navigateTo)
  95. to(path: string) {
  96. this.push({
  97. path
  98. });
  99. }
  100. // 路由跳转,支持多种模式和参数
  101. push(options: PushOptions) {
  102. let {
  103. query = {},
  104. params = {},
  105. mode = "navigateTo",
  106. path,
  107. success,
  108. fail,
  109. complete,
  110. animationType,
  111. animationDuration,
  112. events,
  113. isAuth
  114. } = options;
  115. // 拼接 query 参数到 url
  116. if (!isEmpty(query)) {
  117. const arr = toArray(query, (v, k) => {
  118. return `${k}=${v}`;
  119. });
  120. path += "?" + arr.join("&");
  121. }
  122. // params 通过 storage 临时存储
  123. if (!isEmpty(params)) {
  124. storage.set("router-params", params, 0);
  125. }
  126. // tabBar 页面强制使用 switchTab 跳转
  127. if (this.isTabPage(path)) {
  128. mode = "switchTab";
  129. }
  130. // 跳转执行函数
  131. const next = () => {
  132. switch (mode) {
  133. case "navigateTo":
  134. uni.navigateTo({
  135. url: path,
  136. success,
  137. events,
  138. fail,
  139. complete,
  140. animationType,
  141. animationDuration
  142. });
  143. break;
  144. case "redirectTo":
  145. uni.redirectTo({
  146. url: path,
  147. success,
  148. fail,
  149. complete
  150. });
  151. break;
  152. case "reLaunch":
  153. uni.reLaunch({
  154. url: path,
  155. success,
  156. fail,
  157. complete
  158. });
  159. break;
  160. case "switchTab":
  161. uni.switchTab({
  162. url: path,
  163. success,
  164. fail,
  165. complete
  166. });
  167. break;
  168. }
  169. };
  170. // 跳转前钩子处理
  171. if (this.eventsMap.beforeEach != null) {
  172. // 当前页
  173. const from = last(this.getPages());
  174. // 跳转页
  175. const to = { path, meta: this.getMeta(path), query, isAuth } as RouteInfo;
  176. // 调用跳转前钩子
  177. this.eventsMap.beforeEach(to, from!, next);
  178. } else {
  179. next();
  180. }
  181. }
  182. // 回到首页
  183. home() {
  184. this.push({
  185. path: this.defaultPath("home")
  186. });
  187. }
  188. // 返回上一页
  189. back(options: BackOptions | null = null) {
  190. if (this.isFirstPage()) {
  191. this.home();
  192. } else {
  193. const delta = options?.delta ?? 1;
  194. // 执行跳转函数
  195. const next = () => {
  196. uni.navigateBack({ ...(options ?? {}) });
  197. };
  198. // 跳转前钩子处理
  199. if (this.eventsMap.beforeEach != null) {
  200. // 当前页
  201. const from = last(this.getPages());
  202. // 上一页
  203. const to = nth(this.getPages(), -delta - 1);
  204. if (to != null) {
  205. // 调用跳转前钩子
  206. this.eventsMap.beforeEach(
  207. {
  208. path: to.path,
  209. query: to.query,
  210. meta: to.meta ?? ({} as UTSJSONObject)
  211. },
  212. from!,
  213. next
  214. );
  215. } else {
  216. console.error("[router] found to page is null");
  217. }
  218. } else {
  219. next();
  220. }
  221. }
  222. }
  223. // 获取页面元数据
  224. getMeta(path: string) {
  225. return PAGES.find((e) => e.path.includes(path))?.meta ?? ({} as UTSJSONObject);
  226. }
  227. // 执行当前页面暴露的方法
  228. callMethod(name: string, data?: any): any | null {
  229. const fn = get(this.route()!, `$vm.$.exposed.${name}`) as (d?: any) => any | null;
  230. if (isFunction(fn)) {
  231. return fn(data);
  232. }
  233. return null;
  234. }
  235. // 判断页面栈是否只有一个页面
  236. isFirstPage() {
  237. return getCurrentPages().length == 1;
  238. }
  239. // 判断是否为首页
  240. isHomePage() {
  241. return this.path() == this.defaultPath("home");
  242. }
  243. // 判断是否为自定义导航栏页面
  244. isCustomNavbarPage() {
  245. return this.route()?.isCustomNavbar ?? false;
  246. }
  247. // 判断是否为当前页面
  248. isCurrentPage(path: string) {
  249. return this.path() == path;
  250. }
  251. // 判断是否为 tab 页面
  252. isTabPage(path: string | null = null) {
  253. if (path == null) {
  254. path = this.path();
  255. }
  256. if (path == "/") {
  257. path = this.defaultPath("home");
  258. }
  259. return !isNull(TABS.find((e) => path == e.pagePath));
  260. }
  261. // 判断是否为登录页
  262. isLoginPage(path: string) {
  263. return path == this.defaultPath("login");
  264. }
  265. // 跳转到登录页(防抖处理)
  266. login = debounce(() => {
  267. if (!this.isLoginPage(this.path())) {
  268. this.push({
  269. path: "/pages/user/login",
  270. mode: "reLaunch"
  271. });
  272. }
  273. }, 300);
  274. // 登录成功后跳转逻辑
  275. nextLogin() {
  276. const pages = this.getPages();
  277. // 找到登录页的索引
  278. const index = pages.findIndex((e) => this.defaultPath("login").includes(e.path));
  279. // 未找到,则跳回首页
  280. if (index < 0) {
  281. this.home();
  282. } else {
  283. this.back({
  284. delta: pages.length - index
  285. });
  286. }
  287. // 登录后回调
  288. if (this.eventsMap.afterLogin != null) {
  289. this.eventsMap.afterLogin!();
  290. }
  291. // 触发全局 afterLogin 事件
  292. uni.$emit("afterLogin");
  293. }
  294. // 注册跳转前钩子
  295. beforeEach(cb: BeforeEach) {
  296. this.eventsMap.beforeEach = cb;
  297. }
  298. // 注册登录后回调
  299. afterLogin(cb: AfterLogin) {
  300. this.eventsMap.afterLogin = cb;
  301. }
  302. }
  303. // 单例导出
  304. export const router = new Router();