feat: 登录页路径配置化,支持自定义 loginPageUrl - 新增 refreshTokenKey 配置项 - reLoginParams.loginPageUrl 改为必填 - 移除硬编码 /login,统一使用配置 - 优化 request.ts 响应处理逻辑 - 清空 auto-imports.d.ts何炳宏 authored at 2026-06-18 17:42:59
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')
-}
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'
},
},
};
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;
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>
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>
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 } });
}
}
});
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() {
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;