index.ts 6.8 KB

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