index.ts 6.8 KB

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