Ant design Pro V5 +Typescript + Hooks + Umi + Dev + SpringBoot + mysql + redis + scrity 实现动态菜单权限管理

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',
  });
}



好的,我了解了您的问题。对于这个问题,您需要使用 React Hooks 和 TypeScript 来结合 Ant Design 进行表单组件的二次封装和联动。 首先,您需要在 React 中定义一个表单组件,并使用 useState 钩子来处理输入值的状态。例如: ``` import React, { useState } from 'react'; import { Form, Input, Button } from 'antd'; interface MyFormProps { initialValues?: MyFormValues; onSubmit?: (values: MyFormValues) => void; } interface MyFormValues { name: string; age: number; email: string; } const MyForm: React.FC<MyFormProps> = ({ initialValues = {}, onSubmit }) => { const [name, setName] = useState<string>(initialValues.name ?? ''); const [age, setAge] = useState<number>(initialValues.age ?? 0); const [email, setEmail] = useState<string>(initialValues.email ?? ''); const handleSubmit = (event: React.FormEvent) => { event.preventDefault(); if (onSubmit) { onSubmit({ name, age, email }); } }; const handleNameChange = (event: React.ChangeEvent<HTMLInputElement>) => { setName(event.target.value); }; const handleAgeChange = (event: React.ChangeEvent<HTMLInputElement>) => { setAge(parseInt(event.target.value, 10)); }; const handleEmailChange = (event: React.ChangeEvent<HTMLInputElement>) => { setEmail(event.target.value); }; return ( <Form layout="vertical" onSubmit={handleSubmit}> <Form.Item label="Name"> <Input value={name} onChange={handleNameChange} /> </Form.Item> <Form.Item label="Age"> <Input type="number" value={age} onChange={handleAgeChange} /> </Form.Item> <Form.Item label="Email"> <Input type="email" value={email} onChange={handleEmailChange} /> </Form.Item> <Form.Item> <Button type="primary" htmlType="submit"> Submit </Button> </Form.Item> </Form> ); }; export default MyForm; ``` 接下来,您需要在 Form.Item 组件中使用 getFieldDecorator 来封装每个表单项,并使用 onChange 钩子来处理联动事件。例如: ``` import React from 'react'; import { Form, Input, Button } from 'antd'; interface MyFormProps { initialValues?: MyFormValues; onSubmit?: (values: MyFormValues) => void; } interface MyFormValues { name: string; age: number; email: string; } const MyForm: React.FC<MyFormProps> = ({ initialValues = {}, onSubmit }) => { const handleSubmit = (event: React.FormEvent) => { event.preventDefault(); if (onSubmit) { onSubmit(form.getFieldsValue()); } }; const form = Form.useForm()[0]; const handleNameChange = (event: React.ChangeEvent<HTMLInputElement>) => { form.setFieldsValue({ name: event.target.value }); }; const handleAgeChange = (event: React.ChangeEvent<HTMLInputElement>) => { form.setFieldsValue({ age: parseInt(event.target.value, 10) }); }; const handleEmailChange = (event: React.ChangeEvent<HTMLInputElement>) => { form.setFieldsValue({ email: event.target.value }); }; return ( <Form layout="vertical" onSubmit={handleSubmit} form={form}> <Form.Item label="Name"> {form.getFieldDecorator('name', { initialValue: initialValues.name, })(<Input onChange={handleNameChange} />)} </Form.Item> <Form.Item label="Age"> {form.getFieldDecorator('age', { initialValue: initialValues.age, })(<Input type="number" onChange={handleAgeChange} />)} </Form.Item> <Form.Item label="Email"> {form.getFieldDecorator('email', { initialValue: initialValues.email, })(<Input type="email" onChange={handleEmailChange} />)} </Form.Item> <Form.Item> <Button type="primary" htmlType="submit"> Submit </Button> </Form.Item> </Form> ); }; export default MyForm; ``` 如上所示,您可以使用 getFieldDecorator 来封装每个表单项,并使用 initialValue 属性来设置初始值。在 onChange 钩子中,您可以使用 setFieldsValue 来联动更新其他表单项的值。 这就是使用 React Hooks 和 TypeScript 结合 Ant Design 进行表单组件的二次封装和联动的示例。希望对您有所帮助!
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值