فهرست منبع

feat:路由的接入,部分类似 config.ts、interceptor.ts 的拦截,移植自 unibest3.x 代码

YunaiV 4 ماه پیش
والد
کامیت
ae9451f415
4فایلهای تغییر یافته به همراه202 افزوده شده و 24 حذف شده
  1. 44 0
      src/router/config.ts
  2. 87 2
      src/router/interceptor.ts
  3. 61 20
      src/tabbar/config.ts
  4. 10 2
      src/tabbar/store.ts

+ 44 - 0
src/router/config.ts

@@ -0,0 +1,44 @@
+// import { getAllPages } from '@/utils'
+
+export const LOGIN_STRATEGY_MAP = {
+  DEFAULT_NO_NEED_LOGIN: 0, // 黑名单策略,默认可以进入APP
+  DEFAULT_NEED_LOGIN: 1, // 白名单策略,默认不可以进入APP,需要强制登录
+}
+// TODO: 1/3 登录策略,默认使用`无需登录策略`,即默认不需要登录就可以访问
+export const LOGIN_STRATEGY = LOGIN_STRATEGY_MAP.DEFAULT_NEED_LOGIN // edit by 芋艿:管理后台,默认需要登录
+export const isNeedLoginMode
+  = LOGIN_STRATEGY === LOGIN_STRATEGY_MAP.DEFAULT_NEED_LOGIN
+
+export const LOGIN_PAGE = '/pages/auth/login' // edit by 芋艿:自定义了登录页路径
+export const REGISTER_PAGE = '/pages/auth/register' // edit by 芋艿:自定义了注册页路径
+export const CODE_LOGIN_PAGE = '/pages/auth/code-login' // edit by 芋艿:自定义了短信登录页路径
+export const FORGET_PASSWORD_PAGE = '/pages/auth/forget-password' // edit by 芋艿:自定义了忘记密码页路径
+export const NOT_FOUND_PAGE = '/pages/error/404' // edit by 芋艿:调整 404 页面路径
+
+// TODO @芋艿:【优化】貌似 unibest 这个变量没用?!
+export const LOGIN_PAGE_LIST = [
+  LOGIN_PAGE,
+  REGISTER_PAGE,
+  CODE_LOGIN_PAGE,
+  FORGET_PASSWORD_PAGE,
+]
+
+// 注释 by 芋艿:在 mp 环境下,getAllPages 函数还没初始化好,所以不能直接调用。统一优化到 judgeIsExcludePath 函数里面去获取
+// 在 definePage 里面配置了 excludeLoginPath 的页面,功能与 EXCLUDE_LOGIN_PATH_LIST 相同
+// export const excludeLoginPathList = getAllPages('excludeLoginPath').map(
+//   page => page.path,
+// )
+
+// 排除在外的列表,白名单策略指白名单列表,黑名单策略指黑名单列表
+// TODO: 2/3 在 definePage 配置 excludeLoginPath,或者在下面配置 EXCLUDE_LOGIN_PATH_LIST
+export const EXCLUDE_LOGIN_PATH_LIST = [
+  '/pages/xxx/index', // 示例值
+  '/pages-sub/xxx/index', // 示例值
+  // 注释 by 芋艿:在 mp 环境下,getAllPages 函数还没初始化好,所以不能直接调用。统一优化到 judgeIsExcludePath 函数里面去获取
+  // ...excludeLoginPathList, // 都是以 / 开头的 path
+]
+
+// 在小程序里面是否使用H5的登录页,默认为 false
+// 如果为 true 则复用 h5 的登录逻辑
+// TODO: 3/3 确定自己的登录页是否需要在小程序里面使用
+export const LOGIN_PAGE_ENABLE_IN_MP = true // edit by 芋艿:管理后台,小程序也使用自定义登录页

+ 87 - 2
src/router/interceptor.ts

@@ -1,13 +1,37 @@
+import { isMp } from '@uni-helper/uni-env'
 /**
 /**
  * by 菲鸽 on 2025-08-19
  * by 菲鸽 on 2025-08-19
  * 路由拦截,通常也是登录拦截
  * 路由拦截,通常也是登录拦截
  * 黑、白名单的配置,请看 config.ts 文件, EXCLUDE_LOGIN_PATH_LIST
  * 黑、白名单的配置,请看 config.ts 文件, EXCLUDE_LOGIN_PATH_LIST
  */
  */
-import { tabbarStore } from '@/tabbar/store'
-import { getAllPages, getLastPage, parseUrlToObj } from '@/utils/index'
+import { useTokenStore } from '@/store/token'
+import { isPageTabbar, tabbarStore } from '@/tabbar/store'
+import { getAllPages, getLastPage, HOME_PAGE, parseUrlToObj } from '@/utils/index'
+import { toLoginPage } from '@/utils/toLoginPage'
+import { EXCLUDE_LOGIN_PATH_LIST, isNeedLoginMode, LOGIN_PAGE, LOGIN_PAGE_ENABLE_IN_MP, NOT_FOUND_PAGE } from './config'
 
 
 export const FG_LOG_ENABLE = false
 export const FG_LOG_ENABLE = false
 
 
+let excludeListInited = false // 标记 EXCLUDE_LOGIN_PATH_LIST 是否已经根据 getAllPages('excludeLoginPath') 做过一次性补充(仅非开发环境)
+export function judgeIsExcludePath(path: string) {
+  const isDev = import.meta.env.DEV
+  if (!isDev) {
+    // edit by 芋艿:非开发环境下,只初始化一次
+    if (!excludeListInited) {
+      const pages = getAllPages('excludeLoginPath')
+      pages.forEach((page) => {
+        if (!EXCLUDE_LOGIN_PATH_LIST.includes(page.path)) {
+          EXCLUDE_LOGIN_PATH_LIST.push(page.path)
+        }
+      })
+      excludeListInited = true
+    }
+    return EXCLUDE_LOGIN_PATH_LIST.includes(path)
+  }
+  const allExcludeLoginPages = getAllPages('excludeLoginPath') // dev 环境下,需要每次都重新获取,否则新配置就不会生效
+  return EXCLUDE_LOGIN_PATH_LIST.includes(path) || (isDev && allExcludeLoginPages.some(page => page.path === path))
+}
+
 export const navigateToInterceptor = {
 export const navigateToInterceptor = {
   // 注意,这里的url是 '/' 开头的,如 '/pages/index/index',跟 'pages.json' 里面的 path 不同
   // 注意,这里的url是 '/' 开头的,如 '/pages/index/index',跟 'pages.json' 里面的 path 不同
   // 增加对相对路径的处理,BY 网友 @ideal
   // 增加对相对路径的处理,BY 网友 @ideal
@@ -35,6 +59,7 @@ export const navigateToInterceptor = {
     // 处理路由不存在的情况
     // 处理路由不存在的情况
     if (path !== '/' && !getAllPages().some(page => page.path !== path)) {
     if (path !== '/' && !getAllPages().some(page => page.path !== path)) {
       console.warn('路由不存在:', path)
       console.warn('路由不存在:', path)
+      uni.navigateTo({ url: NOT_FOUND_PAGE })
       return false // 明确表示阻止原路由继续执行
       return false // 明确表示阻止原路由继续执行
     }
     }
 
 
@@ -46,6 +71,66 @@ export const navigateToInterceptor = {
 
 
     // 处理直接进入路由非首页时,tabbarIndex 不正确的问题
     // 处理直接进入路由非首页时,tabbarIndex 不正确的问题
     tabbarStore.setAutoCurIdx(path)
     tabbarStore.setAutoCurIdx(path)
+
+    // 小程序里面使用平台自带的登录,则不走下面的逻辑
+    if (isMp && !LOGIN_PAGE_ENABLE_IN_MP) {
+      return true // 明确表示允许路由继续执行
+    }
+
+    const tokenStore = useTokenStore()
+    FG_LOG_ENABLE && console.log('tokenStore.hasLogin:', tokenStore.hasLogin)
+
+    // 不管黑白名单,登录了就直接去吧(但是当前不能是登录页)
+    if (tokenStore.hasLogin) {
+      if (path !== LOGIN_PAGE) {
+        return true // 明确表示允许路由继续执行
+      } else {
+        console.log('已经登录,但是还在登录页', myQuery.redirect)
+        const url = myQuery.redirect || HOME_PAGE
+        if (isPageTabbar(url)) {
+          uni.switchTab({ url })
+        } else {
+          uni.navigateTo({ url })
+        }
+        return false // 明确表示阻止原路由继续执行
+      }
+    }
+    let fullPath = path
+
+    if (Object.keys(myQuery).length) {
+      fullPath += `?${Object.keys(myQuery).map(key => `${key}=${myQuery[key]}`).join('&')}`
+    }
+    const redirectQuery = `?redirect=${encodeURIComponent(fullPath)}`
+
+    // #region 1/2 默认需要登录的情况(白名单策略) ---------------------------
+    if (isNeedLoginMode) {
+      // 需要登录里面的 EXCLUDE_LOGIN_PATH_LIST 表示白名单,可以直接通过
+      if (judgeIsExcludePath(path)) {
+        return true // 明确表示允许路由继续执行
+      }
+      // 否则需要重定向到登录页
+      else {
+        if (path === LOGIN_PAGE) {
+          return true // 明确表示允许路由继续执行
+        }
+        FG_LOG_ENABLE && console.log('1 isNeedLogin(白名单策略) url:', fullPath)
+        toLoginPage({ queryString: redirectQuery })
+        return false // 明确表示阻止原路由继续执行
+      }
+    }
+    // #endregion 1/2 默认需要登录的情况(白名单策略) ---------------------------
+
+    // #region 2/2 默认不需要登录的情况(黑名单策略) ---------------------------
+    else {
+      // 不需要登录里面的 EXCLUDE_LOGIN_PATH_LIST 表示黑名单,需要重定向到登录页
+      if (judgeIsExcludePath(path)) {
+        FG_LOG_ENABLE && console.log('2 isNeedLogin(黑名单策略) url:', fullPath)
+        toLoginPage({ queryString: redirectQuery })
+        return false // 修改为false,阻止原路由继续执行
+      }
+      return true // 明确表示允许路由继续执行
+    }
+    // #endregion 2/2 默认不需要登录的情况(黑名单策略) ---------------------------
   },
   },
 }
 }
 
 

+ 61 - 20
src/tabbar/config.ts

@@ -34,7 +34,7 @@ export const nativeTabbarList: NativeTabBarItem[] = [
   {
   {
     iconPath: 'static/tabbar/personal.png',
     iconPath: 'static/tabbar/personal.png',
     selectedIconPath: 'static/tabbar/personalHL.png',
     selectedIconPath: 'static/tabbar/personalHL.png',
-    pagePath: 'pages/me/me',
+    pagePath: 'pages/user/index', // edit by 芋艿:原 me 被删除,改为 user 避免 IDE linter 报错
     text: '个人',
     text: '个人',
   },
   },
 ]
 ]
@@ -42,25 +42,25 @@ export const nativeTabbarList: NativeTabBarItem[] = [
 // TODO: 3/3. 使用 CUSTOM_TABBAR(2,3) 时,更新下面的 tabbar 配置
 // TODO: 3/3. 使用 CUSTOM_TABBAR(2,3) 时,更新下面的 tabbar 配置
 // 如果需要配置鼓包,需要在 'tabbar/store.ts' 里面设置,最后在 `tabbar/index.vue` 里面更改鼓包的图片
 // 如果需要配置鼓包,需要在 'tabbar/store.ts' 里面设置,最后在 `tabbar/index.vue` 里面更改鼓包的图片
 export const customTabbarList: CustomTabBarItem[] = [
 export const customTabbarList: CustomTabBarItem[] = [
-  {
-    text: '首页',
-    pagePath: 'pages/index/index',
-    // 注意 unocss 图标需要如下处理:(二选一)
-    // 1)在fg-tabbar.vue页面上引入一下并注释掉(见tabbar/index.vue代码第2行)
-    // 2)配置到 unocss.config.ts 的 safelist 中
-    iconType: 'unocss',
-    icon: 'i-carbon-home',
-    // badge: 'dot',
-  },
-  {
-    pagePath: 'pages/me/me',
-    text: '我的',
-    // 1)在fg-tabbar.vue页面上引入一下并注释掉(见tabbar/index.vue代码第2行)
-    // 2)配置到 unocss.config.ts 的 safelist 中
-    iconType: 'unocss',
-    icon: 'i-carbon-user',
-    // badge: 10,
-  },
+  // {
+  //   text: '首页',
+  //   pagePath: 'pages/index/index',
+  //   // 注意 unocss 图标需要如下处理:(二选一)
+  //   // 1)在fg-tabbar.vue页面上引入一下并注释掉(见tabbar/index.vue代码第2行)
+  //   // 2)配置到 unocss.config.ts 的 safelist 中
+  //   iconType: 'unocss',
+  //   icon: 'i-carbon-home',
+  //   // badge: 'dot',
+  // },
+  // {
+  //   pagePath: 'pages/me/me',
+  //   text: '我的',
+  //   // 1)在fg-tabbar.vue页面上引入一下并注释掉(见tabbar/index.vue代码第2行)
+  //   // 2)配置到 unocss.config.ts 的 safelist 中
+  //   iconType: 'unocss',
+  //   icon: 'i-carbon-user',
+  //   // badge: 10,
+  // },
   // 其他类型演示
   // 其他类型演示
   // 1、uiLib
   // 1、uiLib
   // {
   // {
@@ -86,6 +86,37 @@ export const customTabbarList: CustomTabBarItem[] = [
   //   icon: '/static/tabbar/home.png',
   //   icon: '/static/tabbar/home.png',
   //   iconActive: '/static/tabbar/homeHL.png',
   //   iconActive: '/static/tabbar/homeHL.png',
   // },
   // },
+  // add by 芋艿:图标可到 https://icon-sets.iconify.design/carbon/ 选择。另外,需要在 uno.config.ts 的 safelist 中添加图标类名
+  {
+    text: '工作台',
+    pagePath: 'pages/index/index',
+    iconType: 'unocss',
+    icon: 'i-carbon-home',
+  },
+  {
+    text: '审批',
+    pagePath: 'pages/bpm/index',
+    iconType: 'unocss',
+    icon: 'i-carbon-document',
+  },
+  {
+    text: '通讯录',
+    pagePath: 'pages/contact/index',
+    iconType: 'unocss',
+    icon: 'i-carbon-user-avatar',
+  },
+  {
+    text: '消息',
+    pagePath: 'pages/message/index',
+    iconType: 'unocss',
+    icon: 'i-carbon-chat',
+  },
+  {
+    text: '我的',
+    pagePath: 'pages/user/index',
+    iconType: 'unocss',
+    icon: 'i-carbon-user',
+  },
 ]
 ]
 
 
 /**
 /**
@@ -111,6 +142,16 @@ export const needHideNativeTabbar = selectedTabbarStrategy === TABBAR_STRATEGY_M
 const _tabbarList = customTabbarEnable ? customTabbarList.map(item => ({ text: item.text, pagePath: item.pagePath })) : nativeTabbarList
 const _tabbarList = customTabbarEnable ? customTabbarList.map(item => ({ text: item.text, pagePath: item.pagePath })) : nativeTabbarList
 export const tabbarList = customTabbarEnable ? customTabbarList : nativeTabbarList
 export const tabbarList = customTabbarEnable ? customTabbarList : nativeTabbarList
 
 
+/**
+ * 判断路径是否是 tabBar 页面
+ * @param path 页面路径(支持带或不带 / 前缀)
+ */
+export function isTabBarPage(path: string): boolean {
+  // 统一处理路径格式:去掉开头的 /
+  const normalizedPath = path.startsWith('/') ? path.slice(1) : path
+  return tabbarList.some(item => item.pagePath === normalizedPath)
+}
+
 const _tabbar: TabBar = {
 const _tabbar: TabBar = {
   // 只有微信小程序支持 custom。App 和 H5 不生效
   // 只有微信小程序支持 custom。App 和 H5 不生效
   custom: selectedTabbarStrategy === TABBAR_STRATEGY_MAP.CUSTOM_TABBAR_WITH_CACHE,
   custom: selectedTabbarStrategy === TABBAR_STRATEGY_MAP.CUSTOM_TABBAR_WITH_CACHE,

+ 10 - 2
src/tabbar/store.ts

@@ -1,6 +1,9 @@
 import type { CustomTabBarItem, CustomTabBarItemBadge } from './types'
 import type { CustomTabBarItem, CustomTabBarItemBadge } from './types'
 import { reactive } from 'vue'
 import { reactive } from 'vue'
 
 
+import { isNeedLoginMode } from '@/router/config'
+import { FG_LOG_ENABLE, judgeIsExcludePath } from '@/router/interceptor'
+import { useTokenStore } from '@/store/token'
 import { tabbarList as _tabbarList, customTabbarEnable, selectedTabbarStrategy, TABBAR_STRATEGY_MAP } from './config'
 import { tabbarList as _tabbarList, customTabbarEnable, selectedTabbarStrategy, TABBAR_STRATEGY_MAP } from './config'
 
 
 // TODO 1/2: 中间的鼓包tabbarItem的开关
 // TODO 1/2: 中间的鼓包tabbarItem的开关
@@ -38,8 +41,12 @@ const tabbarStore = reactive({
   curIdx: uni.getStorageSync('app-tabbar-index') || 0,
   curIdx: uni.getStorageSync('app-tabbar-index') || 0,
   prevIdx: uni.getStorageSync('app-tabbar-index') || 0,
   prevIdx: uni.getStorageSync('app-tabbar-index') || 0,
   setCurIdx(idx: number) {
   setCurIdx(idx: number) {
-    this.curIdx = idx
-    uni.setStorageSync('app-tabbar-index', idx)
+    const tokenStore = useTokenStore()
+    // 已登录 或 (url 需要登录 && 在白名单 || 不需要登录 && 不在黑名单) (关于 白名单|黑名单 逻辑: src/router/interceptor.ts)
+    if (tokenStore.hasLogin || (isNeedLoginMode && judgeIsExcludePath(tabbarList[idx].pagePath)) || (!isNeedLoginMode && !judgeIsExcludePath(tabbarList[idx].pagePath))) {
+      this.curIdx = idx
+      uni.setStorageSync('app-tabbar-index', idx)
+    }
   },
   },
   setTabbarItemBadge(idx: number, badge: CustomTabBarItemBadge) {
   setTabbarItemBadge(idx: number, badge: CustomTabBarItemBadge) {
     if (tabbarList[idx]) {
     if (tabbarList[idx]) {
@@ -53,6 +60,7 @@ const tabbarStore = reactive({
       return
       return
     }
     }
     const index = tabbarList.findIndex(item => item.pagePath === path)
     const index = tabbarList.findIndex(item => item.pagePath === path)
+    FG_LOG_ENABLE && console.log('index:', index, path)
     // console.log('tabbarList:', tabbarList)
     // console.log('tabbarList:', tabbarList)
     if (index === -1) {
     if (index === -1) {
       const pagesPathList = getCurrentPages().map(item => item.route.startsWith('/') ? item.route : `/${item.route}`)
       const pagesPathList = getCurrentPages().map(item => item.route.startsWith('/') ? item.route : `/${item.route}`)