interceptor.ts 8.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209
  1. import { isMp } from '@uni-helper/uni-env'
  2. import { useLoadingStore } from '@/store/loading'
  3. /**
  4. * by 菲鸽 on 2025-08-19
  5. * 路由拦截,通常也是登录拦截
  6. * 黑、白名单的配置,请看 config.ts 文件, EXCLUDE_LOGIN_PATH_LIST
  7. */
  8. import { useTokenStore } from '@/store/token'
  9. import { isPageTabbar, tabbarStore } from '@/tabbar/store'
  10. import { getAllPages, getLastPage, HOME_PAGE, parseUrlToObj } from '@/utils/index'
  11. import { EXCLUDE_LOGIN_PATH_LIST, isNeedLoginMode, LOGIN_PAGE, LOGIN_PAGE_ENABLE_IN_MP, NOT_FOUND_PAGE } from './config'
  12. export const FG_LOG_ENABLE = false
  13. // 系统内部页面白名单(不需要检查路由存在性)
  14. const SYSTEM_INTERNAL_PATHS = [
  15. '/__uniappchooselocation', // 选择位置页面
  16. '/__uniapproute', // 路由页面
  17. '/__uniappfilepicker', // 文件选择器
  18. '/__uniappimagepicker', // 图片选择器
  19. ]
  20. export function judgeIsExcludePath(path: string) {
  21. const isDev = import.meta.env.DEV
  22. if (!isDev) {
  23. return EXCLUDE_LOGIN_PATH_LIST.includes(path)
  24. }
  25. const allExcludeLoginPages = getAllPages('excludeLoginPath') // dev 环境下,需要每次都重新获取,否则新配置就不会生效
  26. return EXCLUDE_LOGIN_PATH_LIST.includes(path) || (isDev && allExcludeLoginPages.some(page => page.path === path))
  27. }
  28. // 检查是否为系统内部页面
  29. export function isSystemInternalPath(path: string): boolean {
  30. return SYSTEM_INTERNAL_PATHS.includes(path)
  31. }
  32. // 检查路由是否存在
  33. export function isRouteExists(path: string): boolean {
  34. // 系统内部页面始终认为存在
  35. if (isSystemInternalPath(path)) {
  36. return true
  37. }
  38. const allPages = getAllPages()
  39. return allPages.some(page => page.path === path) || path === '/'
  40. }
  41. export const navigateToInterceptor = {
  42. // 注意,这里的url是 '/' 开头的,如 '/pages/index/index',跟 'pages.json' 里面的 path 不同
  43. // 增加对相对路径的处理,BY 网友 @ideal
  44. invoke({ url, query }: { url: string, query?: Record<string, string> }) {
  45. if (url === undefined) {
  46. return
  47. }
  48. const loadingStore = useLoadingStore()
  49. // 显示加载页
  50. loadingStore.showLoading('页面跳转中...')
  51. let { path, query: _query } = parseUrlToObj(url)
  52. FG_LOG_ENABLE && console.log('\n\n路由拦截器:-------------------------------------')
  53. FG_LOG_ENABLE && console.log('路由拦截器 1: url->', url, ', query ->', query)
  54. const myQuery = { ..._query, ...query }
  55. // /pages/route-interceptor/index?name=feige&age=30
  56. FG_LOG_ENABLE && console.log('路由拦截器 2: path->', path, ', _query ->', _query)
  57. FG_LOG_ENABLE && console.log('路由拦截器 3: myQuery ->', myQuery)
  58. // 处理相对路径
  59. if (!path.startsWith('/')) {
  60. const currentPath = getLastPage()?.route || ''
  61. const normalizedCurrentPath = currentPath.startsWith('/') ? currentPath : `/${currentPath}`
  62. const baseDir = normalizedCurrentPath.substring(0, normalizedCurrentPath.lastIndexOf('/'))
  63. path = `${baseDir}/${path}`
  64. }
  65. // 系统内部页面直接放行(不检查路由存在性,不进行登录拦截)
  66. if (isSystemInternalPath(path)) {
  67. FG_LOG_ENABLE && console.log('系统内部页面,直接放行:', path)
  68. return true
  69. }
  70. // 处理路由不存在的情况
  71. if (!isRouteExists(path)) {
  72. console.warn('路由不存在:', path)
  73. uni.navigateTo({
  74. url: NOT_FOUND_PAGE,
  75. complete: () => {
  76. loadingStore.hideLoading()
  77. }
  78. })
  79. return false // 明确表示阻止原路由继续执行
  80. }
  81. // 处理直接进入路由非首页时,tabbarIndex 不正确的问题
  82. setTimeout(() => {
  83. tabbarStore.setAutoCurIdx(path)
  84. }, 0)
  85. // 小程序里面使用平台自带的登录,则不走下面的逻辑
  86. if (isMp && !LOGIN_PAGE_ENABLE_IN_MP) {
  87. return true // 明确表示允许路由继续执行
  88. }
  89. const tokenStore = useTokenStore()
  90. FG_LOG_ENABLE && console.log('tokenStore.hasLogin:', tokenStore.hasLogin)
  91. // 不管黑白名单,登录了就直接去吧(但是当前不能是登录页)
  92. if (tokenStore.hasLogin) {
  93. if (path !== LOGIN_PAGE) {
  94. return true // 明确表示允许路由继续执行
  95. }
  96. else {
  97. console.log('已经登录,但是还在登录页', myQuery.redirect)
  98. const url = myQuery.redirect || HOME_PAGE
  99. setTimeout(() => {
  100. if (isPageTabbar(url)) {
  101. uni.switchTab({
  102. url,
  103. complete: () => {
  104. loadingStore.hideLoading()
  105. }
  106. })
  107. }
  108. else {
  109. uni.navigateTo({
  110. url,
  111. complete: () => {
  112. loadingStore.hideLoading()
  113. }
  114. })
  115. }
  116. }, 50)
  117. return false // 明确表示阻止原路由继续执行
  118. }
  119. }
  120. let fullPath = path
  121. if (Object.keys(myQuery).length) {
  122. fullPath += `?${Object.keys(myQuery).map(key => `${key}=${myQuery[key]}`).join('&')}`
  123. }
  124. const redirectUrl = `${LOGIN_PAGE}?redirect=${encodeURIComponent(fullPath)}`
  125. // #region 1/2 默认需要登录的情况(白名单策略) ---------------------------
  126. if (isNeedLoginMode) {
  127. // 需要登录里面的 EXCLUDE_LOGIN_PATH_LIST 表示白名单,可以直接通过
  128. if (judgeIsExcludePath(path)) {
  129. return true // 明确表示允许路由继续执行
  130. }
  131. // 否则需要重定向到登录页
  132. else {
  133. if (path === LOGIN_PAGE) {
  134. return true // 明确表示允许路由继续执行
  135. }
  136. FG_LOG_ENABLE && console.log('1 isNeedLogin(白名单策略) redirectUrl:', redirectUrl)
  137. setTimeout(() => {
  138. uni.navigateTo({
  139. url: redirectUrl,
  140. complete: () => {
  141. loadingStore.hideLoading()
  142. }
  143. })
  144. }, 50)
  145. return false // 明确表示阻止原路由继续执行
  146. }
  147. }
  148. // #endregion 1/2 默认需要登录的情况(白名单策略) ---------------------------
  149. // #region 2/2 默认不需要登录的情况(黑名单策略) ---------------------------
  150. else {
  151. // 不需要登录里面的 EXCLUDE_LOGIN_PATH_LIST 表示黑名单,需要重定向到登录页
  152. if (judgeIsExcludePath(path)) {
  153. FG_LOG_ENABLE && console.log('2 isNeedLogin(黑名单策略) redirectUrl:', redirectUrl)
  154. setTimeout(() => {
  155. uni.navigateTo({
  156. url: redirectUrl,
  157. complete: () => {
  158. loadingStore.hideLoading()
  159. }
  160. })
  161. }, 50)
  162. return false // 修改为false,阻止原路由继续执行
  163. }
  164. return true // 明确表示允许路由继续执行
  165. }
  166. // #endregion 2/2 默认不需要登录的情况(黑名单策略) ---------------------------
  167. },
  168. }
  169. // 针对 chooseLocation 的特殊处理
  170. export const chooseLocationInterceptor = {
  171. invoke(options: any) {
  172. // 直接放行 chooseLocation 调用
  173. FG_LOG_ENABLE && console.log('chooseLocation 调用,直接放行:', options)
  174. return true
  175. },
  176. }
  177. export const routeInterceptor = {
  178. install() {
  179. uni.addInterceptor('navigateTo', navigateToInterceptor)
  180. uni.addInterceptor('reLaunch', navigateToInterceptor)
  181. uni.addInterceptor('redirectTo', navigateToInterceptor)
  182. uni.addInterceptor('switchTab', navigateToInterceptor)
  183. // 添加 chooseLocation 的拦截器,确保直接放行
  184. uni.addInterceptor('chooseLocation', chooseLocationInterceptor)
  185. },
  186. }