Ant design Pro V5 +Typescript + Hooks + Umi + Dev + SpringBoot + mysql + redis + scrity 实现动态菜单权限管理(企业中台架构)
1,app.tsx页面配置
该页面集成了登陆权限控制,动态菜单渲染,数据的缓存
import type { BasicLayoutProps, Settings as LayoutSettings } from '@ant-design/pro-layout';
import { PageLoading } from '@ant-design/pro-layout';
import { notification } from 'antd';
import type { RequestConfig, RunTimeLayoutConfig } from 'umi';
import { history, Link } from 'umi';
import RightContent from '@/components/RightContent';
import Footer from '@/components/Footer';
//import { currentUser as queryCurrentUser } from './services/ant-design-pro/api';
// import { ApiFilled, BookOutlined, LinkOutlined } from '@ant-design/icons';
import {userCurrentUser as queryCurrentUser} from './services/user-login/userLogin'
import {userMenuList} from './services/user-menu/userMenu'
import type {MenuDataItem} from '@ant-design/pro-layout'
// import { currentUser } from './services/ant-design-pro/api';
const isDev = process.env.NODE_ENV === 'development';
const loginPath = '/user/login';
/** 获取用户信息比较慢的时候会展示一个 loading */
export const initialStateConfig = {
loading: <PageLoading />,
};
/**
* @see https://umijs.org/zh-CN/plugins/plugin-initial-state
* */
export async function getInitialState(): Promise<{
settings?: Partial<LayoutSettings>;
menuData:MenuDataItem[];
currentUser?: API.CurrentUser;
fetchUserInfo?: (params:any) => Promise<API.CurrentUser | undefined>;
}> {
// user onclick login
const fetchUserInfo = async (params:any) => {
try {
const msg = await queryCurrentUser({userName:params.username,passWord:params.password});
if(msg.data){
console.log("msg.data----------------------------app.tsx-----------------------------------------------")
console.log(msg)
localStorage.setItem("userinfo",JSON.stringify(msg.data));
const menuData = await userMenuList();
console.log(menuData)
localStorage.setItem("menuDataList",JSON.stringify(menuData))
}
return msg.data;
}
catch (error) {
history.push(loginPath);
}
return undefined;
};
//page show
// 如果是登录页面,不执行
if (history.location.pathname !== loginPath) {
// filter
//get
let menuData = JSON.parse(localStorage.getItem("menuDataList"))
let currentUser = JSON.parse(localStorage.getItem("userinfo")) || {}
//const userList = await fetchUserInfo();
console.log("userList-------------------------------")
//console.log(userList)
return {
fetchUserInfo,
currentUser,
menuData,
settings: {},
};
}
console.log("app.tsx--执行------------------------------------------")
return {
fetchUserInfo,
menuData:[],
settings: {},
};
}
// ProLayout 支持的api https://procomponents.ant.design/components/layout
export const layout = ({ initialState }:{
initialState:{settings?: LayoutSettings; menuData:MenuDataItem[];currentUser?:API.CurrentUser}
}):BasicLayoutProps => {
return {
rightContentRender: () => <RightContent />,
disableContentMargin: false,
footerRender: () => <Footer />,
onPageChange: () => {
let menuData = JSON.parse(localStorage.getItem("menuDataList"))
initialState.menuData = menuData
const { location } = history;
//debugger
console.log(history.location.pathname)
// scurity user message
if(initialState.menuData !== null){
console.log("打印userlogin进来了.................")
let loginarrStr = menuData.filter((item:{path:string;}) => item.path === loginPath)
console.log(loginarrStr)
// if(loginarrStr[0].path === loginPath){
// history.push(loginPath)
// return;
// }
console.log("数组不为空进来了---------------------")
let arrStr = menuData.filter((item: { path: string; }) => item.path=== location.pathname)
//let arrStrPro = menuData.filter((item: { path: string; }) => item.path=== '/user/login/str')
console.log(arrStr)
if(arrStr.length > 0){
history.push(arrStr[0].path)
console.log(arrStr)
}else{
history.push('/404')
}
}else{
history.push(loginPath);
}
console.log("app.tsx-------------------------------------------------------------onPageChange---")
// console.log(menuData)
console.log(initialState)
//console.log(menuData)
// 如果没有登录,重定向到 login
if (!initialState?.currentUser && location.pathname !== loginPath) {
history.push(loginPath);
return;
}
},
menuHeaderRender: undefined,
menuDataRender:(menuData)=> initialState.menuData || menuData,
...initialState?.settings,
};
};
2.user/login/index.tsx 页面配置
该页面集成了登陆预加载,判断用户是否存在
import {
AlipayCircleOutlined,
ApiFilled,
LockOutlined,
MobileOutlined,
TaobaoCircleOutlined,
UserOutlined,
WeiboCircleOutlined,
} from '@ant-design/icons';
import { Alert, Space, message, Tabs } from 'antd';
import React, { useState } from 'react';
import ProForm, { ProFormCaptcha, ProFormCheckbox, ProFormText } from '@ant-design/pro-form';
import { useIntl, Link, history, FormattedMessage, SelectLang, useModel } from 'umi';
import Footer from '@/components/Footer';
import { login } from '@/services/ant-design-pro/api';
import { getFakeCaptcha } from '@/services/ant-design-pro/login';
import {userLogins,userCurrentUser} from '@/services/user-login/userLogin'
import styles from './index.less';
import { initial } from 'lodash';
const LoginMessage: React.FC<{
content: string;
}> = ({ content }) => (
<Alert
style={{
marginBottom: 24,
}}
message={content}
type="error"
showIcon
/>
);
const Login: React.FC = () => {
const [submitting, setSubmitting] = useState(false);
const [userLoginState, setUserLoginState] = useState<API.LoginResult>({});
const [type, setType] = useState<string>('account');
const { initialState, setInitialState } = useModel('@@initialState');
const intl = useIntl();
const fetchUserInfo = async (params:API.LoginParams) => {
console.log("start-------------------------reqest ")
const userInfo = await initialState?.fetchUserInfo?.(params);
if (userInfo) {
await setInitialState((s) => ({
...s,
currentUser: userInfo,
}));
}
let currentUsers = JSON.parse(localStorage.getItem("userinfo")) || {}
console.log(currentUsers)
console.log(userInfo)
console.log(initialState)
};
const handleSubmit = async (values: API.LoginParams) => {
setSubmitting(true);
try {
console.log({...values})
// 登录
const msg = await userLogins({ ...values, type });
console.log(msg)
if (msg.status === 'ok') {
const defaultloginSuccessMessage = intl.formatMessage({
id: 'pages.login.success',
defaultMessage: '登录成功!',
});
message.success(defaultloginSuccessMessage);
console.log({...values})
await fetchUserInfo({...values});
/** 此方法会跳转到 redirect 参数所在的位置 */
if (!history) return;
const { query } = history.location;
const { redirect } = query as { redirect: string };
history.push(redirect || '/');
console.log(query) //redirect: "/welcome"
console.log(redirect) ///welcome
console.log(msg) // response new
return;
}
// 如果失败去设置用户错误信息
setUserLoginState(msg);
} catch (error) {
const defaultloginFailureMessage = intl.formatMessage({
id: 'pages.login.failure',
defaultMessage: '登录失败,请重试!',
});
message.error(defaultloginFailureMessage);
}
setSubmitting(false);
};
console.log("print path----------------------------------")
console.log(history.location.pathname)
const { status, type: loginType } = userLoginState;
return (
<div className={styles.container}>
<div className={styles.lang} data-lang>
{SelectLang && <SelectLang />}
</div>
<div className={styles.content}>
<div className={styles.top}>
<div className={styles.header}>
<Link to="/">
<div className={styles.logo}>logo</div>
{/* <img alt="logo" className={styles.logo} src="/logo.svg" /> */}
{/* <span className={styles.title}>Ant Design</span> */}
</Link>
</div>
<div className={styles.desc}>
{intl.formatMessage({ id: 'pages.layouts.userLayout.title' })}
</div>
</div>
<div className={styles.main}>
<ProForm
initialValues={{
autoLogin: true,
}}
submitter={{
searchConfig: {
submitText: intl.formatMessage({
id: 'pages.login.submit',
defaultMessage: '登录',
}),
},
render: (_, dom) => dom.pop(),
submitButtonProps: {
loading: submitting,
size: 'large',
style: {
width: '100%',
},
},
}}
onFinish={async (values) => {
handleSubmit(values as API.LoginParams);
}}
>
{/* <Tabs activeKey={type} onChange={setType}>
<Tabs.TabPane
key="account"
tab={intl.formatMessage({
id: 'pages.login.accountLogin.tab',
defaultMessage: '账户密码登录',
})}
/>
<Tabs.TabPane
key="mobile"
tab={intl.formatMessage({
id: 'pages.login.phoneLogin.tab',
defaultMessage: '手机号登录',
})}
/>
</Tabs> */}
{status === 'error' && loginType === 'account' && (
<LoginMessage
content={intl.formatMessage({
id: 'pages.login.accountLogin.errorMessage',
defaultMessage: '账户或密码错误(admin/ant.design)',
})}
/>
)}
{type === 'account' && (
<>
<ProFormText
name="username"
fieldProps={{
size: 'large',
prefix: <UserOutlined className={styles.prefixIcon} />,
}}
placeholder={intl.formatMessage({
id: 'pages.login.username.placeholder',
defaultMessage: '用户名: admin or user',
})}
//initialValue={"admin"}
rules={[
{
required: true,
message: (
<FormattedMessage
id="pages.login.username.required"
defaultMessage="请输入用户名!"
/>
),
},
]}
/>
<ProFormText.Password
name="password"
fieldProps={{
size: 'large',
prefix: <LockOutlined className={styles.prefixIcon} />,
}}
placeholder={intl.formatMessage({
id: 'pages.login.password.placeholder',
defaultMessage: '密码: ant.design',
})}
initialValue={"ant.design"}
rules={[
{
required: true,
message: (
<FormattedMessage
id="pages.login.password.required"
defaultMessage="请输入密码!"
/>
),
},
]}
/>
</>
)}
{/*
{status === 'error' && loginType === 'mobile' && <LoginMessage content="验证码错误" />}
{type === 'mobile' && (
<>
<ProFormText
fieldProps={{
size: 'large',
prefix: <MobileOutlined className={styles.prefixIcon} />,
}}
name="mobile"
placeholder={intl.formatMessage({
id: 'pages.login.phoneNumber.placeholder',
defaultMessage: '手机号',
})}
rules={[
{
required: true,
message: (
<FormattedMessage
id="pages.login.phoneNumber.required"
defaultMessage="请输入手机号!"
/>
),
},
{
pattern: /^1\d{10}$/,
message: (
<FormattedMessage
id="pages.login.phoneNumber.invalid"
defaultMessage="手机号格式错误!"
/>
),
},
]}
/>
<ProFormCaptcha
fieldProps={{
size: 'large',
prefix: <LockOutlined className={styles.prefixIcon} />,
}}
captchaProps={{
size: 'large',
}}
placeholder={intl.formatMessage({
id: 'pages.login.captcha.placeholder',
defaultMessage: '请输入验证码',
})}
captchaTextRender={(timing, count) => {
if (timing) {
return `${count} ${intl.formatMessage({
id: 'pages.getCaptchaSecondText',
defaultMessage: '获取验证码',
})}`;
}
return intl.formatMessage({
id: 'pages.login.phoneLogin.getVerificationCode',
defaultMessage: '获取验证码',
});
}}
name="captcha"
rules={[
{
required: true,
message: (
<FormattedMessage
id="pages.login.captcha.required"
defaultMessage="请输入验证码!"
/>
),
},
]}
onGetCaptcha={async (phone) => {
const result = await getFakeCaptcha({
phone,
});
if (result === false) {
return;
}
message.success('获取验证码成功!验证码为:1234');
}}
/>
</>
)} */}
<div
style={{
marginBottom: 24,
}}
>
<ProFormCheckbox noStyle name="autoLogin">
<FormattedMessage id="pages.login.rememberMe" defaultMessage="自动登录" />
</ProFormCheckbox>
<a
style={{
float: 'right',
}}
>
<FormattedMessage id="pages.login.forgotPassword" defaultMessage="忘记密码" />
</a>
</div>
</ProForm>
<Space className={styles.other}>
{/* <FormattedMessage id="pages.login.loginWith" defaultMessage="其他登录方式" />
<AlipayCircleOutlined className={styles.icon} />
<TaobaoCircleOutlined className={styles.icon} />
<WeiboCircleOutlined className={styles.icon} /> */}
</Space>
</div>
</div>
<Footer />
</div>
);
};
export default Login;
3,数据的请求Request Umi集成
api user-list的请求事例
// @ts-ignore
/* eslint-disable */
import { request } from 'umi';
/** 获取当前的用户 GET /api/currentUser */
export async function userList(
params?:API.Item
) {
return request<{
data: API.UserItem;
}>('http://localhost:9999/api/user/findAll/'+params?.page+'/'+params?.limit, {
method: 'POST',
data:params
});
}
/**修改用户信息 */
export async function userUpdate(
params:API.Item
){
return request<{data:number}>(
"http://localhost:9999/api/user/update",{
method:"PUT",
data:params
});
}
/**删除用户信息 */
export async function userDelete(id:number){
return request<{data:number}>(
"http://localhost:9999/api/user/delete/"+id,{
method:"DELETE",
});
}
/**新增用户信息 */
export async function userInputAdd(
params:API.Item
){
return request<{data:number}>(
"http://localhost:9999/api/user/input/add",{
method:"POST",
data:params
});
}
/***获取统计信息 */
export async function userCoutnList() {
return request<{
data: API.ZgUserCountList;
}>('http://localhost:9999/api/user/address/count/list', {
method: 'GET',
});
}
api UserLogin请求事例
// @ts-ignore
/* eslint-disable */
import { request } from 'umi';
/** 登录接口 POST /api/login/account */
export async function userLogins(body: API.LoginParams, options?: { [key: string]: any }) {
return request<API.LoginResult>('http://localhost:9999/api/user/login/account', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
data: body,
...(options || {}),
});
}
/** 获取当前的用户 GET /api/currentUser */
export async function userCurrentUser(
params?:API.LoginParams
) {
return request<{
data: API.CurrentUser;
}>('http://localhost:9999/api/user/login/currentUser', {
method: 'POST',
data:params
});
}
api user-menu 动态菜单渲染请求事例
// @ts-ignore
/* eslint-disable */
import { request } from 'umi';
/** 获取当前的用户 GET /api/currentUser */
export async function userMenuList() {
return request<{
data: API.Routes;
}>('http://localhost:9999/api/user/menu/list', {
method: 'GET',
});
}