feat: 路由登录 动态路由重定向

This commit is contained in:
2026-01-26 17:59:52 +08:00
parent 502c236b0d
commit a5d3342d93
3 changed files with 102 additions and 194 deletions

View File

@@ -93,6 +93,7 @@ export const layout: RunTimeLayoutConfig = ({
initialState, initialState,
setInitialState, setInitialState,
}) => { }) => {
const { wsCache } = useCache();
return { return {
actionsRender: () => [ actionsRender: () => [
<Question key="doc" />, <Question key="doc" />,
@@ -101,6 +102,18 @@ export const layout: RunTimeLayoutConfig = ({
menu: { menu: {
locale: false, locale: false,
// 关闭国际化- // 关闭国际化-
// request: async () => {
// const currentUser = wsCache.get(CACHE_KEY.USER);
// console.log("菜单请求被调用", initialState?.menus, currentUser);
// if (currentUser.menus) {
// const menuData = loopMenuItem(currentUser.menus);
// // console.log('转换后的菜单数据:', menuData);
// // const r = loopMenuItem(currentUser.menus);
// return menuData;
// }
// return [];
// },
}, },
avatarProps: { avatarProps: {
src: initialState?.currentUser?.user.avatar, src: initialState?.currentUser?.user.avatar,
@@ -238,7 +251,7 @@ export const request: RequestConfig = {
// Umi 4 支持异步的 patchClientRoutes // Umi 4 支持异步的 patchClientRoutes
export async function patchClientRoutes({ routes }: any) { export async function patchClientRoutes({ routes }: any) {
const { wsCache } = useCache(); const { wsCache } = useCache();
console.log('patchClientRoutes', patchClientRoutes);
// 先尝试从缓存获取 // 先尝试从缓存获取
let menus = wsCache.get(CACHE_KEY.ROLE_ROUTERS); let menus = wsCache.get(CACHE_KEY.ROLE_ROUTERS);

View File

@@ -1,4 +1,3 @@
import { getTenantIdByName, login } from "@/services/login";
import { import {
AlipayOutlined, AlipayOutlined,
LockOutlined, LockOutlined,
@@ -6,34 +5,36 @@ import {
TaobaoOutlined, TaobaoOutlined,
UserOutlined, UserOutlined,
WeiboOutlined, WeiboOutlined,
} from "@ant-design/icons"; } from '@ant-design/icons';
import { import {
LoginFormPage, LoginFormPage,
ProConfigProvider, ProConfigProvider,
ProFormCaptcha, ProFormCaptcha,
ProFormCheckbox, ProFormCheckbox,
ProFormText, ProFormText,
} from "@ant-design/pro-components"; } from '@ant-design/pro-components';
import { history, useModel, useNavigate } from "@umijs/max"; import { history, useModel, useNavigate } from '@umijs/max';
import { Button, Divider, Space, Tabs, theme, message } from "antd"; import { Button, Divider, message, Space, Tabs, theme } from 'antd';
import type { CSSProperties } from "react"; import type { CSSProperties } from 'react';
import { useState } from "react"; import { useState } from 'react';
import * as authUtil from "@/utils/auth"; import { flushSync } from 'react-dom';
import { flushSync } from "react-dom"; import { getTenantIdByName, login } from '@/services/login';
type LoginType = "phone" | "account"; import * as authUtil from '@/utils/auth';
type LoginType = 'phone' | 'account';
const iconStyles: CSSProperties = { const iconStyles: CSSProperties = {
color: "rgba(0, 0, 0, 0.2)", color: 'rgba(0, 0, 0, 0.2)',
fontSize: "18px", fontSize: '18px',
verticalAlign: "middle", verticalAlign: 'middle',
cursor: "pointer", cursor: 'pointer',
}; };
const Page = () => { const Page = () => {
const [loginType, setLoginType] = useState<LoginType>("account"); const [loginType, setLoginType] = useState<LoginType>('account');
const { token } = theme.useToken(); const { token } = theme.useToken();
const [messageApi, contextHolder] = message.useMessage(); const [messageApi, contextHolder] = message.useMessage();
const { initialState, setInitialState } = useModel("@@initialState"); const { initialState, setInitialState } = useModel('@@initialState');
const navigate = useNavigate(); const navigate = useNavigate();
// 获取租户 ID // 获取租户 ID
const getTenantId = async (name: string) => { const getTenantId = async (name: string) => {
@@ -57,7 +58,7 @@ const Page = () => {
try { try {
// 根据登录类型处理不同的参数 // 根据登录类型处理不同的参数
const params = { const params = {
tenantName: "芋道源码", tenantName: '芋道源码',
...values, ...values,
}; };
@@ -71,21 +72,22 @@ const Page = () => {
// 调用登录接口 // 调用登录接口
const data = await login(params); const data = await login(params);
// 登录成功 // 登录成功
messageApi.success("登录成功!"); messageApi.success('登录成功!');
// 跳转到首页 // 跳转到首页
authUtil.setToken(data); authUtil.setToken(data);
await fetchUserInfo(); await fetchUserInfo();
const urlParams = new URL(window.location.href).searchParams; const urlParams = new URL(window.location.href).searchParams;
navigate(urlParams.get("redirect") || "/"); // navigate(urlParams.get("redirect") || "/");
window.location.href = urlParams.get('redirect') || '/';
} catch (error) { } catch (error) {
messageApi.error("登录失败,请检查网络或稍后重试!"); messageApi.error('登录失败,请检查网络或稍后重试!');
} }
}; };
return ( return (
<div <div
style={{ style={{
backgroundColor: "white", backgroundColor: 'white',
height: "100vh", height: '100vh',
}} }}
> >
{contextHolder} {contextHolder}
@@ -95,8 +97,8 @@ const Page = () => {
backgroundVideoUrl="https://gw.alipayobjects.com/v/huamei_gcee1x/afts/video/jXRBRK_VAwoAAAAAAAAAAAAAK4eUAQBr" backgroundVideoUrl="https://gw.alipayobjects.com/v/huamei_gcee1x/afts/video/jXRBRK_VAwoAAAAAAAAAAAAAK4eUAQBr"
// title="BY" // title="BY"
containerStyle={{ containerStyle={{
backgroundColor: "rgb(0 0 0 / 51%)", backgroundColor: 'rgb(0 0 0 / 51%)',
backdropFilter: "blur(4px)", backdropFilter: 'blur(4px)',
}} }}
onFinish={handleSubmit} onFinish={handleSubmit}
subTitle="百业到家云控台" subTitle="百业到家云控台"
@@ -127,17 +129,17 @@ const Page = () => {
actions={ actions={
<div <div
style={{ style={{
display: "flex", display: 'flex',
justifyContent: "center", justifyContent: 'center',
alignItems: "center", alignItems: 'center',
flexDirection: "column", flexDirection: 'column',
}} }}
> >
<Divider plain> <Divider plain>
<span <span
style={{ style={{
color: token.colorTextPlaceholder, color: token.colorTextPlaceholder,
fontWeight: "normal", fontWeight: 'normal',
fontSize: 14, fontSize: 14,
}} }}
> >
@@ -147,48 +149,48 @@ const Page = () => {
<Space align="center" size={24}> <Space align="center" size={24}>
<div <div
style={{ style={{
display: "flex", display: 'flex',
justifyContent: "center", justifyContent: 'center',
alignItems: "center", alignItems: 'center',
flexDirection: "column", flexDirection: 'column',
height: 40, height: 40,
width: 40, width: 40,
border: "1px solid " + token.colorPrimaryBorder, border: '1px solid ' + token.colorPrimaryBorder,
background: token.colorBgContainer, background: token.colorBgContainer,
borderRadius: "50%", borderRadius: '50%',
}} }}
> >
<AlipayOutlined style={{ ...iconStyles, color: "#1677FF" }} /> <AlipayOutlined style={{ ...iconStyles, color: '#1677FF' }} />
</div> </div>
<div <div
style={{ style={{
display: "flex", display: 'flex',
justifyContent: "center", justifyContent: 'center',
alignItems: "center", alignItems: 'center',
flexDirection: "column", flexDirection: 'column',
height: 40, height: 40,
width: 40, width: 40,
border: "1px solid " + token.colorPrimaryBorder, border: '1px solid ' + token.colorPrimaryBorder,
background: token.colorBgContainer, background: token.colorBgContainer,
borderRadius: "50%", borderRadius: '50%',
}} }}
> >
<TaobaoOutlined style={{ ...iconStyles, color: "#FF6A10" }} /> <TaobaoOutlined style={{ ...iconStyles, color: '#FF6A10' }} />
</div> </div>
<div <div
style={{ style={{
display: "flex", display: 'flex',
justifyContent: "center", justifyContent: 'center',
alignItems: "center", alignItems: 'center',
flexDirection: "column", flexDirection: 'column',
height: 40, height: 40,
width: 40, width: 40,
background: token.colorBgContainer, background: token.colorBgContainer,
border: "1px solid " + token.colorPrimaryBorder, border: '1px solid ' + token.colorPrimaryBorder,
borderRadius: "50%", borderRadius: '50%',
}} }}
> >
<WeiboOutlined style={{ ...iconStyles, color: "#1890ff" }} /> <WeiboOutlined style={{ ...iconStyles, color: '#1890ff' }} />
</div> </div>
</Space> </Space>
</div> </div>
@@ -199,125 +201,125 @@ const Page = () => {
activeKey={loginType} activeKey={loginType}
onChange={(activeKey) => setLoginType(activeKey as LoginType)} onChange={(activeKey) => setLoginType(activeKey as LoginType)}
> >
<Tabs.TabPane key={"account"} tab={"账号密码登录"} /> <Tabs.TabPane key={'account'} tab={'账号密码登录'} />
<Tabs.TabPane key={"phone"} tab={"手机号登录"} /> <Tabs.TabPane key={'phone'} tab={'手机号登录'} />
</Tabs> </Tabs>
{loginType === "account" && ( {loginType === 'account' && (
<> <>
<ProFormText <ProFormText
name="username" name="username"
fieldProps={{ fieldProps={{
size: "large", size: 'large',
prefix: ( prefix: (
<UserOutlined <UserOutlined
style={{ style={{
color: token.colorText, color: token.colorText,
}} }}
className={"prefixIcon"} className={'prefixIcon'}
/> />
), ),
style: { style: {
backgroundColor: "rgb(0 0 0 / 77%)", backgroundColor: 'rgb(0 0 0 / 77%)',
color: "white", color: 'white',
}, },
}} }}
placeholder={"用户名: admin or user"} placeholder={'用户名: admin or user'}
rules={[ rules={[
{ {
required: true, required: true,
message: "请输入用户名!", message: '请输入用户名!',
}, },
]} ]}
/> />
<ProFormText.Password <ProFormText.Password
name="password" name="password"
fieldProps={{ fieldProps={{
size: "large", size: 'large',
prefix: ( prefix: (
<LockOutlined <LockOutlined
style={{ style={{
color: token.colorText, color: token.colorText,
}} }}
className={"prefixIcon"} className={'prefixIcon'}
/> />
), ),
style: { style: {
backgroundColor: "rgb(0 0 0 / 77%)", backgroundColor: 'rgb(0 0 0 / 77%)',
color: "white", color: 'white',
}, },
}} }}
placeholder={"密码: ant.design"} placeholder={'密码: ant.design'}
rules={[ rules={[
{ {
required: true, required: true,
message: "请输入密码!", message: '请输入密码!',
}, },
]} ]}
/> />
</> </>
)} )}
{loginType === "phone" && ( {loginType === 'phone' && (
<> <>
<ProFormText <ProFormText
fieldProps={{ fieldProps={{
size: "large", size: 'large',
prefix: ( prefix: (
<MobileOutlined <MobileOutlined
style={{ style={{
color: token.colorText, color: token.colorText,
}} }}
className={"prefixIcon"} className={'prefixIcon'}
/> />
), ),
style: { style: {
backgroundColor: "rgb(0 0 0 / 77%)", backgroundColor: 'rgb(0 0 0 / 77%)',
color: "white", color: 'white',
}, },
}} }}
name="mobile" name="mobile"
placeholder={"手机号"} placeholder={'手机号'}
rules={[ rules={[
{ {
required: true, required: true,
message: "请输入手机号!", message: '请输入手机号!',
}, },
{ {
pattern: /^1\d{10}$/, pattern: /^1\d{10}$/,
message: "手机号格式错误!", message: '手机号格式错误!',
}, },
]} ]}
/> />
<ProFormCaptcha <ProFormCaptcha
fieldProps={{ fieldProps={{
size: "large", size: 'large',
prefix: ( prefix: (
<LockOutlined <LockOutlined
style={{ style={{
color: token.colorText, color: token.colorText,
}} }}
className={"prefixIcon"} className={'prefixIcon'}
/> />
), ),
}} }}
captchaProps={{ captchaProps={{
size: "large", size: 'large',
}} }}
placeholder={"请输入验证码"} placeholder={'请输入验证码'}
captchaTextRender={(timing, count) => { captchaTextRender={(timing, count) => {
if (timing) { if (timing) {
return `${count} ${"获取验证码"}`; return `${count} ${'获取验证码'}`;
} }
return "获取验证码"; return '获取验证码';
}} }}
name="captcha" name="captcha"
rules={[ rules={[
{ {
required: true, required: true,
message: "请输入验证码!", message: '请输入验证码!',
}, },
]} ]}
onGetCaptcha={async () => { onGetCaptcha={async () => {
message.success("获取验证码成功验证码为1234"); message.success('获取验证码成功验证码为1234');
}} }}
/> />
</> </>
@@ -332,7 +334,7 @@ const Page = () => {
</ProFormCheckbox> </ProFormCheckbox>
<a <a
style={{ style={{
float: "right", float: 'right',
}} }}
> >

View File

@@ -2,7 +2,10 @@ import { Spin } from 'antd';
import React from 'react'; import React from 'react';
import type { MenuVO } from '@/services/system/menu'; import type { MenuVO } from '@/services/system/menu';
export const loopMenuItem = (menus: MenuVO[], pId: number | string): any[] => { export const loopMenuItem = (
menus: MenuVO[],
pId: number | string = '/',
): any[] => {
return menus.map((item) => { return menus.map((item) => {
let Component: React.ComponentType<any> | null = null; let Component: React.ComponentType<any> | null = null;
if (item.component && item.component.trim().length > 0) { if (item.component && item.component.trim().length > 0) {
@@ -59,113 +62,3 @@ export const loopMenuItem = (menus: MenuVO[], pId: number | string): any[] => {
return routeItem; return routeItem;
}); });
}; };
// return menus.flatMap((item) => {
// let Component: React.ComponentType<any> | null = null;
// if (item.component && item.component.length > 0) {
// // 防止配置了路由,但本地暂未添加对应的页面,产生的错误
// Component = React.lazy(() => {
// const importComponent = () => import(`@/pages/${item.component}`);
// const import404 = () => import("@/pages/404");
// return importComponent().catch(import404);
// });
// }
// if (item.children && item.children.length > 0) {
// return [
// {
// path: item.path,
// hideInMenu: false,
// parentId: pId,
// id: item.id,
// children: [...loopMenuItem(item.children, item.id)], // 添加缺失的 children 属性
// },
// ];
// } else {
// return [
// {
// path: item.path,
// name: item.name,
// // icon: item.icon,
// id: item.id,
// parentId: pId,
// hideInMenu: !item.visible,
// element: (
// <React.Suspense
// fallback={<Spin style={{ width: "100%", height: "100%" }} />}
// >
// {Component && <Component />}
// </React.Suspense>
// ),
// },
// ];
// }
// });
// return [];
// };
// function getFirstLeafPath(menus: any[], parentPath: string): string {
// const firstMenu = menus[0];
// const currentPath = `${parentPath}/${firstMenu.path}`;
// if (firstMenu.children && firstMenu.children.length > 0) {
// if (!firstMenu.hideInMenu) {
// return getFirstLeafPath(firstMenu.children, currentPath);
// } else {
// return getFirstLeafPath(firstMenu.children, parentPath);
// }
// } else {
// return currentPath;
// }
// }
// src/utils/route.ts
import { lazy } from 'react';
export interface RouteItem {
id: number;
parentId: number;
name: string;
path: string;
component: string | null;
componentName: string;
icon: string;
visible: boolean;
keepAlive: boolean;
alwaysShow: boolean;
children: RouteItem[] | null;
}
/**
* 转换后端路由数据为 Ant Design Pro 路由格式
*/
export function transformRoutes(routes: MenuVO[]): any[] {
return routes
.filter((route) => route.visible) // 只显示可见路由
.map((route) => {
const routeConfig: any = {
path: route.path,
name: route.name,
icon: route.icon || undefined,
};
// 处理组件
if (route.component) {
routeConfig.component = lazy(
() =>
import(`@/pages/${route.component}`).catch(
() => import('@/pages/404'),
), // 组件不存在时显示404
);
}
// 处理子路由
if (route.children && route.children.length > 0) {
routeConfig.routes = transformRoutes(route.children);
}
// 隐藏菜单但保留路由
if (!route.visible) {
routeConfig.hideInMenu = true;
}
return routeConfig;
});
}