NewLife/cube-front

feat: 登录页路径配置化,支持自定义 loginPageUrl

- 新增 refreshTokenKey 配置项
- reLoginParams.loginPageUrl 改为必填
- 移除硬编码 /login,统一使用配置
- 优化 request.ts 响应处理逻辑
- 清空 auto-imports.d.ts
何炳宏 authored at 2026-06-18 17:42:59
e0b66bc
Tree
1 Parent(s) ce0bb20
Summary: 8 changed files with 78 additions and 116 deletions.
Modified +0 -78
Modified +3 -1
Modified +3 -2
Modified +8 -1
Modified +10 -4
Modified +12 -5
Modified +8 -1
Modified +34 -24
Modified +0 -78
diff --git a/auto-imports.d.ts b/auto-imports.d.ts
index aa08566..e69de29 100644
--- a/auto-imports.d.ts
+++ b/auto-imports.d.ts
@@ -1,78 +0,0 @@
-/* eslint-disable */
-/* prettier-ignore */
-// @ts-nocheck
-// noinspection JSUnusedGlobalSymbols
-// Generated by unplugin-auto-import
-// biome-ignore lint: disable
-export {}
-declare global {
-  const EffectScope: typeof import('vue')['EffectScope']
-  const ElMessage: typeof import('element-plus/es')['ElMessage']
-  const ElMessageBox: typeof import('element-plus/es')['ElMessageBox']
-  const computed: typeof import('vue')['computed']
-  const createApp: typeof import('vue')['createApp']
-  const customRef: typeof import('vue')['customRef']
-  const defineAsyncComponent: typeof import('vue')['defineAsyncComponent']
-  const defineComponent: typeof import('vue')['defineComponent']
-  const effectScope: typeof import('vue')['effectScope']
-  const getCurrentInstance: typeof import('vue')['getCurrentInstance']
-  const getCurrentScope: typeof import('vue')['getCurrentScope']
-  const h: typeof import('vue')['h']
-  const inject: typeof import('vue')['inject']
-  const isProxy: typeof import('vue')['isProxy']
-  const isReactive: typeof import('vue')['isReactive']
-  const isReadonly: typeof import('vue')['isReadonly']
-  const isRef: typeof import('vue')['isRef']
-  const markRaw: typeof import('vue')['markRaw']
-  const nextTick: typeof import('vue')['nextTick']
-  const onActivated: typeof import('vue')['onActivated']
-  const onBeforeMount: typeof import('vue')['onBeforeMount']
-  const onBeforeRouteLeave: typeof import('vue-router')['onBeforeRouteLeave']
-  const onBeforeRouteUpdate: typeof import('vue-router')['onBeforeRouteUpdate']
-  const onBeforeUnmount: typeof import('vue')['onBeforeUnmount']
-  const onBeforeUpdate: typeof import('vue')['onBeforeUpdate']
-  const onDeactivated: typeof import('vue')['onDeactivated']
-  const onErrorCaptured: typeof import('vue')['onErrorCaptured']
-  const onMounted: typeof import('vue')['onMounted']
-  const onRenderTracked: typeof import('vue')['onRenderTracked']
-  const onRenderTriggered: typeof import('vue')['onRenderTriggered']
-  const onScopeDispose: typeof import('vue')['onScopeDispose']
-  const onServerPrefetch: typeof import('vue')['onServerPrefetch']
-  const onUnmounted: typeof import('vue')['onUnmounted']
-  const onUpdated: typeof import('vue')['onUpdated']
-  const onWatcherCleanup: typeof import('vue')['onWatcherCleanup']
-  const provide: typeof import('vue')['provide']
-  const reactive: typeof import('vue')['reactive']
-  const readonly: typeof import('vue')['readonly']
-  const ref: typeof import('vue')['ref']
-  const resolveComponent: typeof import('vue')['resolveComponent']
-  const shallowReactive: typeof import('vue')['shallowReactive']
-  const shallowReadonly: typeof import('vue')['shallowReadonly']
-  const shallowRef: typeof import('vue')['shallowRef']
-  const toRaw: typeof import('vue')['toRaw']
-  const toRef: typeof import('vue')['toRef']
-  const toRefs: typeof import('vue')['toRefs']
-  const toValue: typeof import('vue')['toValue']
-  const triggerRef: typeof import('vue')['triggerRef']
-  const unref: typeof import('vue')['unref']
-  const useAttrs: typeof import('vue')['useAttrs']
-  const useCssModule: typeof import('vue')['useCssModule']
-  const useCssVars: typeof import('vue')['useCssVars']
-  const useId: typeof import('vue')['useId']
-  const useLink: typeof import('vue-router')['useLink']
-  const useModel: typeof import('vue')['useModel']
-  const useRoute: typeof import('vue-router')['useRoute']
-  const useRouter: typeof import('vue-router')['useRouter']
-  const useSlots: typeof import('vue')['useSlots']
-  const useTemplateRef: typeof import('vue')['useTemplateRef']
-  const watch: typeof import('vue')['watch']
-  const watchEffect: typeof import('vue')['watchEffect']
-  const watchPostEffect: typeof import('vue')['watchPostEffect']
-  const watchSyncEffect: typeof import('vue')['watchSyncEffect']
-}
-// for type re-export
-declare global {
-  // @ts-ignore
-  export type { Component, Slot, Slots, ComponentPublicInstance, ComputedRef, DirectiveBinding, ExtractDefaultPropTypes, ExtractPropTypes, ExtractPublicPropTypes, InjectionKey, PropType, Ref, MaybeRef, MaybeRefOrGetter, VNode, WritableComputedRef } from 'vue'
-  import('vue')
-}
Modified +3 -1
diff --git a/core/configure/defaultConfig/index.ts b/core/configure/defaultConfig/index.ts
index ff6589c..6140f6f 100644
--- a/core/configure/defaultConfig/index.ts
+++ b/core/configure/defaultConfig/index.ts
@@ -74,6 +74,7 @@ export const defaultConfig: CubeFrontConfig = {
   },
   auth: {
     tokenKey: 'token',
+    refreshTokenKey: 'refresh_token',
     // 后面会自动拼接重定向地址即可,后台需要配置SsoSafeDomains,跳转地址允许白名单,否则不能正常重定向
     oauthUrl: '/Sso/Login?name=NewLife&source=front-end&redirect_uri=',
     redirectUrl: '/login',
@@ -85,8 +86,9 @@ export const defaultConfig: CubeFrontConfig = {
         url: '/Admin/User/Logout',
       };
     },
+    // 默认的重新登录参数
     reLoginParams: {
-      // 默认的重新登录参数
+      loginPageUrl: '/login'
     },
   },
 };
Modified +3 -2
diff --git a/core/configure/types.d.ts b/core/configure/types.d.ts
index 7269566..25ed939 100644
--- a/core/configure/types.d.ts
+++ b/core/configure/types.d.ts
@@ -84,6 +84,7 @@ export interface RequestConfig {
 // 认证相关配置
 export interface AuthConfig {
   tokenKey: string;
+  refreshTokenKey: string;
   oauthUrl: string;
   redirectUrl?: string;
   pageTitle?: string;
@@ -92,7 +93,7 @@ export interface AuthConfig {
   | AxiosRequestConfig
   | (() => AxiosRequestConfig)
   | (() => Promise<AxiosRequestConfig>);
-  reLoginParams?: {
+  reLoginParams: {
     titleIntlCode?: string;
     titleIntlDefault?: string;
     messageIntlCode?: string;
@@ -101,7 +102,7 @@ export interface AuthConfig {
     okTextIntlDefault?: string;
     cancelTextIntlCode?: string;
     cancelTextIntlDefault?: string;
-    loginPageUrl?: string;
+    loginPageUrl: string;
     cancelText?: string;
     onModalShow?: () => void;
     onOk?: () => void;
Modified +8 -1
diff --git a/core/layouts/RootLayout.vue b/core/layouts/RootLayout.vue
index 97cdc19..48588a5 100644
--- a/core/layouts/RootLayout.vue
+++ b/core/layouts/RootLayout.vue
@@ -8,6 +8,7 @@ import { useMenuStore } from '../stores/menu';
 import { getUrlHashToken } from '../utils/token';
 import { useLayout } from '../composables/useLayout';
 import TopMenuLayout from './TopMenu/index.vue'; // 兜底默认布局
+import { getConfig } from '../configure/index.ts';
 
 const router = useRouter();
 const route = useRoute();
@@ -22,6 +23,12 @@ const MainLayout = computed(() => currentComponent.value ?? TopMenuLayout);
 // 使用计算属性获取响应式的 meta 对象
 const meta = computed(() => route.meta);
 
+const {
+  auth: {
+    reLoginParams: { loginPageUrl },
+  },
+} = getConfig();
+
 function checkLogin() {
   const token = getUrlHashToken();
 
@@ -64,7 +71,7 @@ onMounted(async () => {
 
 <template>
   <!-- 如果是登录页面,直接返回 -->
-  <template v-if="route.path === '/login'">
+  <template v-if="route.path === loginPageUrl">
     <slot />
   </template>
 
Modified +10 -4
diff --git a/core/pages/PageUnauthorized.vue b/core/pages/PageUnauthorized.vue
index e3a5393..1a32233 100644
--- a/core/pages/PageUnauthorized.vue
+++ b/core/pages/PageUnauthorized.vue
@@ -21,12 +21,18 @@
 </template>
 
 <script setup lang="ts">
-import { ElButton, ElRow, ElCol } from 'element-plus'
-import { useRouter } from 'vue-router'
+import { ElButton, ElRow, ElCol } from 'element-plus';
+import { useRouter } from 'vue-router';
+import { getConfig } from '../configure';
 
-const router = useRouter()
+const router = useRouter();
+const {
+  auth: {
+    reLoginParams: { loginPageUrl },
+  },
+} = getConfig();
 function goLogin() {
-  router.replace({ path: '/login' })
+  router.replace({ path: loginPageUrl });
 }
 </script>
 
Modified +12 -5
diff --git a/core/router/index.ts b/core/router/index.ts
index e9a1ab9..cc61b00 100644
--- a/core/router/index.ts
+++ b/core/router/index.ts
@@ -7,6 +7,7 @@ import { getAccessToken } from '../utils/token';
 import { getUrlHashToken } from '../utils/token';
 import { registerMenuRoutes } from '../utils/menuRoutes';
 import { normalizeMenuUrl } from '../utils/url';
+import { getConfig } from '../configure';
 
 // 创建路由实例
 const router: Router = createRouter({
@@ -14,6 +15,12 @@ const router: Router = createRouter({
   routes,
 });
 
+const {
+  auth: {
+    reLoginParams: { loginPageUrl },
+  },
+} = getConfig();
+
 // 先初始化微前端应用路由(异步操作)
 // 我们不会等待其完成再导出router,但会在导航守卫中检查初始化状态
 initAppRoutes(router).catch((error) => {
@@ -54,7 +61,7 @@ router.beforeEach(async (to, from, next) => {
   }
 
   // 检查微应用路由是否已初始化完成
-  if (!isRoutesInitialized() && to.path !== '/login') {
+  if (!isRoutesInitialized()) {
     // 可以选择显示加载页面或重定向到特定页面
     console.log('等待微应用路由初始化完成...');
     // 可以添加一个加载中的页面
@@ -73,14 +80,14 @@ router.beforeEach(async (to, from, next) => {
   const requireAuth = to.meta?.auth ?? true;
 
   // 如果已经在登录页且要去的也是登录页,直接放行
-  if (from.path === '/login' && to.path === '/login') {
+  if (to.path === loginPageUrl) {
     return next();
   }
 
   // 如果有token
   if (hasToken) {
     // 如果去登录页,直接跳转到首页
-    if (to.path === '/login') {
+    if (to.path === loginPageUrl) {
       if (to.query.redirect) {
         next({ path: to.query.redirect as string });
       } else {
@@ -94,7 +101,7 @@ router.beforeEach(async (to, from, next) => {
         } catch (error) {
           // 获取用户信息失败,可能是token无效,跳转到登录页
           console.error('获取用户信息失败:', error);
-          next({ path: '/login' });
+          next({ path: loginPageUrl });
           return;
         }
       }
@@ -137,7 +144,7 @@ router.beforeEach(async (to, from, next) => {
       next();
     } else {
       // 需要认证的路由,重定向到登录页
-      next({ path: '/login', query: { redirect: to.fullPath } });
+      next({ path: loginPageUrl, query: { redirect: to.fullPath } });
     }
   }
 });
Modified +8 -1
diff --git a/core/stores/user.ts b/core/stores/user.ts
index 830e06a..5e1a89e 100644
--- a/core/stores/user.ts
+++ b/core/stores/user.ts
@@ -4,6 +4,13 @@ import { type AxiosRequestConfig } from 'axios';
 import request from '../utils/request';
 import { removeAccessToken } from '../utils/token';
 import { useMenuStore } from './menu';
+import { gotoPage } from '../utils/router';
+
+const {
+  auth: {
+    reLoginParams: { loginPageUrl },
+  },
+} = getConfig();
 
 /** 用户信息 */
 export interface UserInfo {
@@ -106,7 +113,7 @@ export const useUserStore = defineStore('user', {
         removeAccessToken();
 
         // 重定向到登录页
-        window.location.href = '/login';
+        gotoPage(loginPageUrl);
       }
     },
     async fetchUserInfoAsync() {
Modified +34 -24
diff --git a/core/utils/request.ts b/core/utils/request.ts
index 2bfffc3..793b12b 100644
--- a/core/utils/request.ts
+++ b/core/utils/request.ts
@@ -19,6 +19,13 @@ import notification from '../components/Notification';
 
 import { intl } from '../i18n';
 
+const {
+  request: { baseUrl: API_HOST },
+  auth: { oauthUrl, reLoginParams: {
+    loginPageUrl,
+  } },
+} = getConfig();
+
 // 常量定义
 const BASE_PATH = '';
 const INDEX_ROUTE_PATH = '/';
@@ -28,16 +35,13 @@ const INDEX_ROUTE_PATH = '/';
  * @param {Object} options - 配置选项
  * @param {string} options.loginPageUrl - 可选的登录页URL
  */
-export function redirectToLogin({ loginPageUrl }: { loginPageUrl?: string } = {}) {
+export function redirectToLogin({ loginPageUrl: loginPageUrl2 }: { loginPageUrl?: string; } = {}) {
   removeAccessToken();
   removeAllCookie();
 
-  const {
-    request: { baseUrl: API_HOST },
-    auth: { oauthUrl },
-  } = getConfig();
-  console.log('redirectToLogin', API_HOST);
-  const LOGIN_URL = loginPageUrl || `${API_HOST}${oauthUrl}`;
+
+  const LOGIN_URL = loginPageUrl2 || loginPageUrl || `${API_HOST}${oauthUrl}`;
+  console.log('redirectToLogin', LOGIN_URL);
 
   const sessionData = getSession('redirectUrl');
   let cacheLocation = sessionData;
@@ -52,12 +56,14 @@ export function redirectToLogin({ loginPageUrl }: { loginPageUrl?: string } = {}
   //   });
   // }
 
+  const loginPath = LOGIN_URL;
+
   // 构建重定向URL
   const redirectParams = getSession('templateParams') || '';
-  if (LOGIN_URL.includes('?')) {
-    window.location.href = `${LOGIN_URL}&redirect_uri=${cacheLocation}${redirectParams}`;
+  if (loginPath.includes('?')) {
+    gotoPage(`${loginPath}&redirect_uri=${cacheLocation}${redirectParams}`);
   } else {
-    window.location.href = `${LOGIN_URL}?redirect_uri=${cacheLocation}${redirectParams}`;
+    gotoPage(`${loginPath}?redirect_uri=${cacheLocation}${redirectParams}`);
   }
 }
 
@@ -253,14 +259,15 @@ function handleResponseError(error: AxiosError) {
  * @returns {any} - 处理后的响应数据,直接返回data部分
  */
 function handleResponseSuccess(response: AxiosResponse) {
-  const { data, config } = response;
+  const { data, config, headers } = response;
+  const contentType = headers['content-type'] || '';
 
   // 处理文件下载等二进制数据响应
-  if (config.responseType === 'blob' || config.responseType === 'arraybuffer') {
+  if (contentType === 'application/octet-stream' || contentType === 'arraybuffer') {
     return data;
   }
 
-  if (config.responseType === 'text' && typeof data !== 'string') {
+  if (contentType === 'text' && typeof data !== 'string') {
     return JSON.stringify(data);
   }
 
@@ -283,17 +290,20 @@ function handleResponseSuccess(response: AxiosResponse) {
     const isSuccess = apiResponse.code === 0 || apiResponse.code === 200;
 
     if (isSuccess) {
-      // 如果有分页信息,返回包含data和page的对象
-      if (apiResponse.page) {
-        return {
-          data: apiResponse.data,
-          page: apiResponse.page,
-          stat: apiResponse.stat,
-        };
-      }
-
-      // 无分页信息,直接返回data部分
-      return apiResponse.data;
+      // 保持统一,返回原始数据,固定的响应结构,避免每个接口返回结构不一样
+      return apiResponse;
+
+      // // 如果有分页信息,返回包含data和page的对象
+      // if (apiResponse.page) {
+      //   return {
+      //     data: apiResponse.data,
+      //     page: apiResponse.page,
+      //     stat: apiResponse.stat,
+      //   };
+      // }
+
+      // // 无分页信息,直接返回data部分
+      // return apiResponse.data;
     } else {
       // 失败:自动显示错误提示
       let errorMessage: string;