Commit 07405b7b by User

登录模块优化,路由验证优化

parent 70990881
<!doctype html>
<html lang="zh-cmn-Hans">
<head>
<meta name="buildTime" content="2025-06-10 20:28:50">
<meta name="buildTime" content="2025-06-16 14:15:36">
<meta charset="UTF-8" />
<link rel="icon" href="/favicon.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta name="color-scheme" content="light dark" />
<title>VueDashboard</title>
<script type="module" crossorigin src="/Content/VueDashboardUi/VueDashboard1/assets/index-B_dt3SP8.js"></script>
<script type="module" crossorigin src="/Content/VueDashboardUi/VueDashboard1/assets/index-C1MVrFI5.js"></script>
<link rel="stylesheet" crossorigin href="/Content/VueDashboardUi/VueDashboard1/assets/index-BLjiwC98.css">
</head>
<body>
......
......@@ -124,7 +124,15 @@ export function useRouterPush(inSetup = true) {
const redirect = route.value.query?.redirect as string;
if (redirect) {
routerPush(redirect);
// 清理redirect参数,避免循环重定向
const cleanRedirect = redirect.split('?')[0]; // 只保留路径部分,去掉查询参数
// 确保不是回到登录页
if (cleanRedirect !== '/login') {
routerPush(cleanRedirect);
} else {
toHome();
}
} else {
toHome();
}
......
......@@ -5,8 +5,6 @@ import VueGridLayout from 'vue-grid-layout';
import MateChat from '@matechat/core';
import '@devui-design/icons/icomoon/devui-icon.css';
import './plugins/assets';
import { localStg } from '@/utils/storage';
// main.js or main.ts
import 'font-awesome/css/font-awesome.css';
// 引入 KaTeX 样式以支持数学公式渲染
import 'katex/dist/katex.min.css';
......@@ -16,46 +14,6 @@ import { setupRouter } from './router';
import { setupI18n } from './locales';
import App from './App.vue';
// let timeoutHandle: string | number | NodeJS.Timeout | undefined;
// const INACTIVITY_TIMEOUT = 20 * 60 * 1000;
// function handleLogout(app: any) {
// localStg.remove('token');
// localStg.remove('refreshToken');
// localStg.remove('userInfo');
// app.config.globalProperties.$router.push('/login/pwd-login');
// }
// function checkLastActivity(app: any) {
// const lastActivity = localStorage.getItem('lastActivityTime');
// if (lastActivity) {
// const timeDiff = Date.now() - Number.parseInt(lastActivity, 10);
// if (timeDiff > INACTIVITY_TIMEOUT) {
// handleLogout(app);
// }
// }
// }
// function startInactivityTimer(app: any) {
// clearTimeout(timeoutHandle);
// localStorage.setItem('lastActivityTime', Date.now().toString());
// timeoutHandle = setTimeout(() => handleLogout(app), INACTIVITY_TIMEOUT);
// }
// function resetInactivityTimer(app: any) {
// startInactivityTimer(app);
// }
// 检查认证状态
// function checkAuthStatus(app: any) {
// if (window.uiGlobalConfig?.IsAuthenticated === false) {
// localStg.remove('token');
// localStg.remove('refreshToken');
// localStg.remove('userInfo');
// app.config.globalProperties.$router.push('/login/pwd-login');
// }
// }
async function setupApp() {
setupLoading();
setupNProgress();
......@@ -66,37 +24,14 @@ async function setupApp() {
setupStore(app);
await setupRouter(app);
setupI18n(app);
// 检查认证状态
// checkAuthStatus(app);
// 检查上次活动时间
// checkLastActivity(app);
// 监听用户的操作
// window.addEventListener('mousemove', () => resetInactivityTimer(app));
// window.addEventListener('keydown', () => resetInactivityTimer(app));
// window.addEventListener('click', () => resetInactivityTimer(app));
// window.addEventListener('scroll', () => resetInactivityTimer(app));
// 启动计时器
// startInactivityTimer(app);
app.use(VueGridLayout);
app.use(MateChat);
// 全局注册 wangeditor 组件
app.component('WangEditor', Editor);
app.component('WangToolbar', Toolbar);
app.mount('#app');
}
// 页面卸载时保存最后活动时间
// window.addEventListener('beforeunload', () => {
// localStorage.setItem('lastActivityTime', Date.now().toString());
// });
setTimeout(() => {
setupApp();
}, 200);
import type {
LocationQueryRaw,
NavigationGuardNext,
RouteLocationNormalized,
RouteLocationRaw,
Router
} from 'vue-router';
import type { NavigationGuardNext, RouteLocationNormalized, RouteLocationRaw, Router } from 'vue-router';
import type { RouteKey, RoutePath } from '@elegant-router/types';
import { getRouteName } from '@/router/elegant/transform';
import { useAuthStore } from '@/store/modules/auth';
import { useRouteStore } from '@/store/modules/route';
import { localStg } from '@/utils/storage';
......@@ -27,20 +20,46 @@ export function createRouteGuard(router: Router) {
const authStore = useAuthStore();
const rootRoute: RouteKey = 'root';
const loginRoute: RouteKey = 'login';
const noAuthorizationRoute: RouteKey = '403';
const isAuthenticated = window.uiGlobalConfig?.IsAuthenticated !== false;
// IsAuthenticated 字段具有决定性权威
const globalIsAuthenticated = window.uiGlobalConfig?.IsAuthenticated;
const hasLocalToken = Boolean(localStg.get('token'));
// 如果 IsAuthenticated 明确为 false,则必须跳转登录页
if (globalIsAuthenticated === false) {
// 清除所有本地认证数据
localStg.remove('token');
localStg.remove('refreshToken');
localStg.remove('userInfo');
const needLogin = !to.meta.constant;
if (needLogin) {
next({ name: loginRoute, query: { redirect: to.fullPath } });
return;
}
}
// 如果 IsAuthenticated 为 true 或 undefined,则检查 token 状态
const isAuthenticated = globalIsAuthenticated === true || (globalIsAuthenticated === undefined && hasLocalToken);
const needLogin = !to.meta.constant;
const routeRoles = to.meta.roles || [];
const hasRole = authStore.userInfo.roles.some(role => routeRoles.includes(role));
const hasAuth = authStore.isStaticSuper || !routeRoles.length || hasRole;
// 如果有token但uiGlobalConfig未设置,则设置认证状态
if (hasLocalToken && globalIsAuthenticated === undefined && window.uiGlobalConfig) {
window.uiGlobalConfig.IsAuthenticated = true;
}
// 如果未认证,清除token并跳转登录页
if (!isAuthenticated && needLogin) {
localStg.remove('token');
localStg.remove('refreshToken');
localStg.remove('userInfo');
next({ name: loginRoute, query: { redirect: to.fullPath } });
return;
}
......@@ -174,20 +193,3 @@ function handleRouteSwitch(to: RouteLocationNormalized, from: RouteLocationNorma
next();
}
function getRouteQueryOfLoginRoute(to: RouteLocationNormalized, routeHome: RouteKey) {
const loginRoute: RouteKey = 'login';
const redirect = to.fullPath;
const [redirectPath, redirectQuery] = redirect.split('?');
const redirectName = getRouteName(redirectPath as RoutePath);
const isRedirectHome = routeHome === redirectName;
const query: LocationQueryRaw = to.name !== loginRoute && !isRedirectHome ? { redirect } : {};
if (isRedirectHome && redirectQuery) {
query.redirect = `/?${redirectQuery}`;
}
return query;
}
/* eslint-disable no-plusplus */
import { truncate } from 'node:fs/promises';
import type { CustomRoute, ElegantConstRoute, ElegantRoute } from '@elegant-router/types';
import type { ElegantConstRoute } from '@elegant-router/types';
import { getRootMenu } from '@/service/api';
import { useRouteStore } from '@/store/modules/route';
import { generatedRoutes } from '../elegant/routes';
import { layouts, views } from '../elegant/imports';
import { transformElegantRoutesToVueRoutes } from '../elegant/transform';
import { generatedRoutes } from '../elegant/routes';
type ElegantRoute = ElegantConstRoute;
/**
* custom routes
*
* @link https://github.com/soybeanjs/elegant-router?tab=readme-ov-file#custom-route
*/
const customRoutes: CustomRoute[] = [
const customRoutes: ElegantRoute[] = [
// {
// name: 'exception',
// path: '/exception',
......@@ -56,8 +58,6 @@ const customRoutes: CustomRoute[] = [
// }
// ]
// },
// 以下是iframe-page的示例
// {
// name: 'document',
......@@ -186,37 +186,49 @@ const customRoutes: CustomRoute[] = [
// ]
// }
];
/**
* Get MenuThree
*
* @param MenuThree MenuThree
*/
// 以下是获取菜单的示例
// ${window.uiGlobalConfig.InternalCode}
// const { data: menus } = await getRootMenu(`/Restful/Kivii.Basic.Entities.Menu/Show.json?RootInternalCode=dashboard`);
const { data: menus } = await getRootMenu(
// 将动态菜单路由存储在全局变量中
let dynamicMenuRoutes: ElegantRoute[] = [];
// 动态获取菜单数据并生成路由
export async function generateDynamicRoutes() {
try {
// 清空之前的动态路由
dynamicMenuRoutes = [];
if (!window.uiGlobalConfig?.InternalCode) {
console.warn('InternalCode not found, skipping dynamic route generation');
return;
}
const { data: menus } = await getRootMenu(
`/Restful/Kivii.Basic.Entities.Menu/Show.json?RootInternalCode=${window.uiGlobalConfig.InternalCode}`
);
);
const MenuThree = await getMenuThree(menus?.MenusMain?.Results);
const MenuRoot = menus?.MenuRoot;
// // console.log(MenuRoot);
// // 存储 MenuRoot 到 store
const MenuThree = getMenuThree(menus?.MenusMain?.Results);
const MenuRoot = menus?.MenuRoot;
if (MenuRoot) {
// 存储 MenuRoot 到 store
if (MenuRoot) {
setTimeout(() => {
const routeStore = useRouteStore();
routeStore.setMenuRoot(MenuRoot);
}, 1000);
}
}, 100);
}
const MenuThree2 = generateRoutes(MenuThree);
if (MenuThree2.length > 0) {
for (let i = 0; i < MenuThree2.length; i++) {
customRoutes.push(MenuThree2[i]);
const MenuThree2 = generateRoutes(MenuThree);
if (MenuThree2.length > 0) {
dynamicMenuRoutes = [...MenuThree2];
}
} catch (error) {
console.error('Failed to generate dynamic routes:', error);
}
}
function getMenuThree(data: any) {
if (!data || !Array.isArray(data)) return [];
const cloneData = JSON.parse(JSON.stringify(data)); // 对源数据深度克隆
return cloneData.filter((father: { Kvid: any; children: any; ParentKvid: undefined }) => {
const branchArr = cloneData.filter((child: { ParentKvid: any }) => father.Kvid === child.ParentKvid);
......@@ -226,7 +238,10 @@ function getMenuThree(data: any) {
return father.ParentKvid == undefined;
});
}
function generateRoutes(data: any[]) {
if (!data || !Array.isArray(data)) return [];
return data.map(item => {
const route: any = {
name: item.Type === 'System' ? `${item.Type}` : item.Kvid,
......@@ -270,11 +285,12 @@ function generateRoutes(data: any[]) {
return route;
});
}
export function createStaticRoutes() {
const constantRoutes: ElegantRoute[] = [];
const authRoutes: ElegantRoute[] = [];
[...customRoutes, ...generatedRoutes].forEach(item => {
[...customRoutes, ...dynamicMenuRoutes, ...generatedRoutes].forEach(item => {
if (item.meta?.constant) {
constantRoutes.push(item);
} else {
......@@ -296,3 +312,14 @@ export function createStaticRoutes() {
export function getAuthVueRoutes(routes: ElegantConstRoute[]) {
return transformElegantRoutesToVueRoutes(routes, layouts, views);
}
// 初始化时生成一次动态路由(兼容性)
// 确保在浏览器环境且必要的全局配置已准备好时才执行
if (typeof window !== 'undefined' && window.uiGlobalConfig?.InternalCode) {
// 使用 setTimeout 确保应用完全初始化后再执行
setTimeout(() => {
generateDynamicRoutes().catch(error => {
console.warn('Initial dynamic route generation failed:', error);
});
}, 100);
}
import { json } from 'node:stream/consumers';
import { computed, reactive, ref } from 'vue';
import { useRoute } from 'vue-router';
import { defineStore } from 'pinia';
import { useLoading } from '@sa/hooks';
import { SetupStoreId } from '@/enum';
import { useRouterPush } from '@/hooks/common/router';
import { fetchGetUserInfo, fetchLogin, fetchLogout } from '@/service/api';
import { fetchLogin, fetchLogout } from '@/service/api';
import { localStg } from '@/utils/storage';
import { $t } from '@/locales';
import { useRouteStore } from '../route';
......@@ -81,6 +80,42 @@ export const useAuthStore = defineStore(SetupStoreId.Auth, () => {
}
}
/** Handle successful login redirect */
async function handleLoginRedirect() {
try {
await redirectFromLogin();
// 显示成功通知
window.$notification?.success({
title: $t('page.login.common.loginSuccess'),
content: $t('page.login.common.welcomeBack', { FullName: userInfo.FullName }),
duration: 4500
});
} catch (redirectError) {
console.error('Redirect error:', redirectError);
// 如果重定向失败,直接跳转到首页
window.location.href = '/home';
}
}
/** Initialize routes after login */
async function initializeAuthRoutes(redirect: boolean) {
await routeStore.forceReloadAuthRoute();
if (!routeStore.isInitAuthRoute) {
console.error('Failed to initialize auth routes');
throw new Error('Route initialization failed');
}
// 确保菜单也更新了
await routeStore.updateGlobalMenusByLocale();
if (redirect) {
// 延迟执行重定向,确保路由完全加载
setTimeout(() => handleLoginRedirect(), 300);
}
}
/**
* Login
*
......@@ -91,37 +126,35 @@ export const useAuthStore = defineStore(SetupStoreId.Auth, () => {
async function login(userName: string, password: string, redirect = true) {
startLoading();
try {
const { data: loginToken, error } = await fetchLogin(userName, password);
if (!error) {
if (error) {
resetStore();
return;
}
const pass = await loginByToken(loginToken);
if (!pass) {
resetStore();
return;
}
if (pass) {
// 设置认证状态为true
if (window.uiGlobalConfig) {
window.uiGlobalConfig.IsAuthenticated = true;
}
// 初始化路由
await routeStore.initAuthRoute();
// 等待路由初始化完成
if (routeStore.isInitAuthRoute) {
if (redirect) {
// 先执行重定向
await redirectFromLogin();
// 延迟一下再刷新,确保重定向已完成
try {
await initializeAuthRoutes(redirect);
} catch (routeError) {
console.error('Route loading error:', routeError);
// 如果路由初始化失败,尝试刷新页面作为备用方案
setTimeout(() => {
window.location.reload();
window.$notification?.success({
title: $t('page.login.common.loginSuccess'),
content: $t('page.login.common.welcomeBack', { FullName: userInfo.FullName }),
duration: 4500
});
}, 500);
}
}, 1000);
}
}
} else {
} catch (loginError) {
console.error('Login error:', loginError);
resetStore();
}
......@@ -135,30 +168,12 @@ export const useAuthStore = defineStore(SetupStoreId.Auth, () => {
localStg.set('userInfo', JSON.stringify(loginToken));
// 2. get user info
// const pass = await getUserInfo();
// if (pass) {
token.value = loginToken.FullName;
Object.assign(userInfo, loginToken);
// return true;
// }
return true;
}
async function getUserInfo() {
const { data: info, error } = await fetchGetUserInfo();
if (!error) {
// update store
Object.assign(userInfo, info);
return true;
}
return false;
}
async function initUserInfo() {
const hasToken = getToken();
const userInfoStr = getLocalUserInfo();
......
......@@ -5,7 +5,7 @@ import { useBoolean } from '@sa/hooks';
import type { CustomRoute, ElegantConstRoute, LastLevelRouteKey, RouteKey, RouteMap } from '@elegant-router/types';
import { SetupStoreId } from '@/enum';
import { router } from '@/router';
import { createStaticRoutes, getAuthVueRoutes } from '@/router/routes';
import { createStaticRoutes, generateDynamicRoutes, getAuthVueRoutes } from '@/router/routes';
import { ROOT_ROUTE } from '@/router/routes/builtin';
import { getRouteName, getRoutePath } from '@/router/elegant/transform';
import { fetchGetConstantRoutes, fetchGetUserRoutes, fetchIsRouteExist } from '@/service/api';
......@@ -227,6 +227,24 @@ export const useRouteStore = defineStore(SetupStoreId.Route, () => {
tabStore.initHomeTab();
}
/** Force reload auth route - 强制重新加载路由 */
async function forceReloadAuthRoute() {
// 重置路由状态
setIsInitAuthRoute(false);
// 清除现有路由
resetVueRoutes();
// 清除现有的auth routes
authRoutes.value = [];
// 重新生成动态路由数据
await generateDynamicRoutes();
// 重新初始化路由
await initAuthRoute();
}
/** Init static auth route */
function initStaticAuthRoute() {
const { authRoutes: staticAuthRoutes } = createStaticRoutes();
......@@ -246,9 +264,10 @@ export const useRouteStore = defineStore(SetupStoreId.Route, () => {
/** Init dynamic auth route */
async function initDynamicAuthRoute() {
// 首先尝试使用后端接口获取路由
const { data, error } = await fetchGetUserRoutes();
if (!error) {
if (!error && data?.routes) {
const { routes, home } = data;
addAuthRoutes(routes);
......@@ -261,8 +280,22 @@ export const useRouteStore = defineStore(SetupStoreId.Route, () => {
setIsInitAuthRoute(true);
} else {
// if fetch user routes failed, reset store
authStore.resetStore();
// 如果后端接口获取失败,使用静态路由模式
console.warn('Dynamic route fetch failed, fallback to static routes');
// 重新创建静态路由(这会重新调用接口获取最新菜单数据)
const { authRoutes: staticAuthRoutes } = createStaticRoutes();
if (authStore.isStaticSuper) {
addAuthRoutes(staticAuthRoutes);
} else {
const filteredAuthRoutes = filterAuthRoutesByRoles(staticAuthRoutes, authStore.userInfo.roles);
addAuthRoutes(filteredAuthRoutes);
}
handleConstantAndAuthRoutes();
setIsInitAuthRoute(true);
}
}
......@@ -381,6 +414,7 @@ export const useRouteStore = defineStore(SetupStoreId.Route, () => {
initConstantRoute,
isInitConstantRoute,
initAuthRoute,
forceReloadAuthRoute,
isInitAuthRoute,
setIsInitAuthRoute,
getIsAuthRouteExist,
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment