diff --git a/src/router/config.ts b/src/router/config.ts new file mode 100644 index 0000000..9082fc8 --- /dev/null +++ b/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 芋艿:管理后台,小程序也使用自定义登录页 diff --git a/src/router/interceptor.ts b/src/router/interceptor.ts index b4092d5..39a199c 100644 --- a/src/router/interceptor.ts +++ b/src/router/interceptor.ts @@ -1,13 +1,37 @@ +import { isMp } from '@uni-helper/uni-env' /** * by 菲鸽 on 2025-08-19 * 路由拦截,通常也是登录拦截 * 黑、白名单的配置,请看 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 +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 = { // 注意,这里的url是 '/' 开头的,如 '/pages/index/index',跟 'pages.json' 里面的 path 不同 // 增加对相对路径的处理,BY 网友 @ideal @@ -35,6 +59,7 @@ export const navigateToInterceptor = { // 处理路由不存在的情况 if (path !== '/' && !getAllPages().some(page => page.path !== path)) { console.warn('路由不存在:', path) + uni.navigateTo({ url: NOT_FOUND_PAGE }) return false // 明确表示阻止原路由继续执行 } @@ -46,6 +71,66 @@ export const navigateToInterceptor = { // 处理直接进入路由非首页时,tabbarIndex 不正确的问题 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 默认不需要登录的情况(黑名单策略) --------------------------- }, } diff --git a/src/tabbar/config.ts b/src/tabbar/config.ts index 141c07a..4e09d2c 100644 --- a/src/tabbar/config.ts +++ b/src/tabbar/config.ts @@ -34,7 +34,7 @@ export const nativeTabbarList: NativeTabBarItem[] = [ { iconPath: 'static/tabbar/personal.png', selectedIconPath: 'static/tabbar/personalHL.png', - pagePath: 'pages/me/me', + pagePath: 'pages/user/index', // edit by 芋艿:原 me 被删除,改为 user 避免 IDE linter 报错 text: '个人', }, ] @@ -42,25 +42,25 @@ export const nativeTabbarList: NativeTabBarItem[] = [ // TODO: 3/3. 使用 CUSTOM_TABBAR(2,3) 时,更新下面的 tabbar 配置 // 如果需要配置鼓包,需要在 'tabbar/store.ts' 里面设置,最后在 `tabbar/index.vue` 里面更改鼓包的图片 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 // { @@ -86,6 +86,37 @@ export const customTabbarList: CustomTabBarItem[] = [ // icon: '/static/tabbar/home.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 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 = { // 只有微信小程序支持 custom。App 和 H5 不生效 custom: selectedTabbarStrategy === TABBAR_STRATEGY_MAP.CUSTOM_TABBAR_WITH_CACHE, diff --git a/src/tabbar/store.ts b/src/tabbar/store.ts index c26b826..5b07cbd 100644 --- a/src/tabbar/store.ts +++ b/src/tabbar/store.ts @@ -1,6 +1,9 @@ import type { CustomTabBarItem, CustomTabBarItemBadge } from './types' 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' // TODO 1/2: 中间的鼓包tabbarItem的开关 @@ -38,8 +41,12 @@ const tabbarStore = reactive({ curIdx: uni.getStorageSync('app-tabbar-index') || 0, prevIdx: uni.getStorageSync('app-tabbar-index') || 0, 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) { if (tabbarList[idx]) { @@ -53,6 +60,7 @@ const tabbarStore = reactive({ return } const index = tabbarList.findIndex(item => item.pagePath === path) + FG_LOG_ENABLE && console.log('index:', index, path) // console.log('tabbarList:', tabbarList) if (index === -1) { const pagesPathList = getCurrentPages().map(item => item.route.startsWith('/') ? item.route : `/${item.route}`)