index.ts 5.8 KB

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