如何使用react 18 + antd (v5.3.0) 实现用户登录功能及用户权限校验?

作为一个企业内部系统,在用户还没有登录的前提下,是不允许访问系统中任何页面内容的。

所以本节要实现的:

  • 实现一个登录页面,在用户没有登录时访问系统只能看到这个登录页面的内容
  • 对于之前内容进行角色和权限判断

另外为了避免刷新页面后登录状态重置,还要保存一下用户的登录信息。

这需要用到浏览器的 localstorage 功能

本文只是提供一种思路,具体,可以根据项目进行自行修改。

Dropdown下拉菜单

如何实现触发事件?

点击菜单项后会触发事件,用户可以通过相应的菜单项 key 进行不同的操作。

import { DownOutlined } from '@ant-design/icons';
import { Dropdown, message, Space } from 'antd';

// 注意此处
const onClick = ({ key }) => {
  message.info(`Click on item ${key}`);
};

const items = [
  {
    label: '1st menu item',
    key: '1',
  },
  {
    label: '2nd menu item',
    key: '2',
  },
  {
    label: '3rd menu item',
    key: '3',
  },
];
const App = () => (
  <Dropdown
    menu={{
      items,
      onClick, // 注意此处
    }}
  >
    <a onClick={(e) => e.preventDefault()}>
      <Space>
        Hover me, Click menu item
        <DownOutlined />
      </Space>
    </a>
  </Dropdown>
);
export default App;

跳转到登录

实现代码

import {  useNavigate } from "react-router-dom";

const navigate = useNavigate();  

const onClick = ({ key }) => {  
  if(key.toString() === '4'){
    navigate('/login');
  }
};

<div style={{ width: "40px", margin: "0 0 0 10px" }}>
  <Dropdown
    menu={{
      items,
      onClick,
    }}
  >
    <a
      onClick={(e) => {
        e.preventDefault();
      }}
    >
      <Space>
        <Avatar size={25} icon={<UserOutlined />} />
      </Space>
    </a>
  </Dropdown>
</div>

布局登录页面

登录框

普通的登录框,可以容纳更多的元素。

实现代码

import { LockOutlined, UserOutlined } from "@ant-design/icons";
import { Button, Form, Input, Space } from "antd";

import styles from "./index.module.css"; 

export default function Login() {
  const onFinish = (values) => {
    console.log("Received values of form: ");
    console.log(values);
  };

  return (
    <div className={styles["login-page-container"]} style={{}}>
    <i className={styles["mask"]}></i>
      <div className={styles["login-wrapper"]}>
        <h2>后台系统</h2>
        <Form name="normal_login" className="login-form" onFinish={onFinish}>
          <Form.Item
            name="username"
            rules={[
              {
                required: true,
                message: "请输入用户名!",
              },
            ]}
          >
            <Input
              prefix={<UserOutlined className="site-form-item-icon" />}
              placeholder="请输入用户名"
            />
          </Form.Item>
          <Form.Item
            name="password"
            rules={[
              {
                required: true,
                message: "请输入密码",
              },
            ]}
          >
            <Input
              prefix={<LockOutlined className="site-form-item-icon" />}
              type="password"
              placeholder="请输入密码"
            />
          </Form.Item>

          <Form.Item>
            <Space size="large" style={{width: '100%',justifyContent: 'center'}}>
              <Button
                type="primary"
                htmlType="submit"
                className="login-form-button"
              >
                登录
              </Button>
              <a href="#" style={{color: '#fff'}}>注册</a>
            </Space>
          </Form.Item>
        </Form>
      </div>
    </div>
  );
}

页面展示

登录校验

reactjs-localstorage react存储本地数据,下载地址

安装

npm install reactjs-localstorage
or 
yarn add reactjs-localstorage

使用案例

import {reactLocalStorage} from 'reactjs-localstorage';
 
reactLocalStorage.set('var', true);
reactLocalStorage.get('var', true);
reactLocalStorage.setObject('var', {'test': 'test'});
reactLocalStorage.getObject('var');
reactLocalStorage.remove('var');
reactLocalStorage.clear();

jsonserver由于受限,所以,暂时使用get请求。但是真实环境,一定是后端判断是否可以登录

实现代码

import {reactLocalStorage} from 'reactjs-localstorage'; //引入组件 'reactjs-localstorage'

const onFinish = async (values) => { 
  let userData = await fetchGetUsersLogin(values) 

  // 判断是否是可用用户
  if(userData.length > 0){ 
    reactLocalStorage.setObject('token', userData[0]);
    navigate('/home');
  } else {
    message.error(`登录失败`);
  }
}; 

页面展示

在实际中,后端返回的token往往是一大串大串的字符串,而不是真实用户数据。

全部代码

import { LockOutlined, UserOutlined } from "@ant-design/icons";
import { Button, Form, Input, Space ,message} from "antd";
import {  useNavigate } from "react-router-dom";

import styles from "./index.module.css";

import { fetchGetUsersLogin } from "../../utils/api";

import {reactLocalStorage} from 'reactjs-localstorage';

export default function Login() {
  const navigate = useNavigate(); 

  const onFinish = async (values) => { 
    let userData = await fetchGetUsersLogin(values) 

    // 判断是否是可用用户
    if(userData.length > 0){ 
      delete  userData[0].password
      reactLocalStorage.setObject('token', userData[0]);
      navigate('/home');
    } else {
      message.error(`登录失败`);
    }
  };

  return (
    <div className={styles["login-page-container"]} style={{}}>
      <i className={styles["mask"]}></i>
      <div className={styles["login-wrapper"]}>
        <h2>后台系统</h2>
        <Form name="normal_login" className="login-form" onFinish={onFinish}>
          <Form.Item
            name="username"
            rules={[
              {
                required: true,
                message: "请输入用户名!",
              },
            ]}
          >
            <Input
              prefix={<UserOutlined className="site-form-item-icon" />}
              placeholder="请输入用户名"
            />
          </Form.Item>
          <Form.Item
            name="password"
            rules={[
              {
                required: true,
                message: "请输入密码",
              },
            ]}
          >
            <Input
              prefix={<LockOutlined className="site-form-item-icon" />}
              type="password"
              placeholder="请输入密码"
            />
          </Form.Item>

          <Form.Item>
            <Space
              size="large"
              style={{ width: "100%", justifyContent: "center" }}
            >
              <Button
                type="primary"
                htmlType="submit"
                className="login-form-button"
              >
                登录
              </Button>
              <a href="#" style={{ color: "#fff" }}>
                注册
              </a>
            </Space>
          </Form.Item>
        </Form>
      </div>
    </div>
  );
}

权限校验

此部分,需要修改之前的左侧导航组件:需要根据用户的权限,动态显示左侧导航内容。

详见左侧导航组件内容

修改之前组件,添加用户权限判断

import {reactLocalStorage} from 'reactjs-localstorage';

// 获取用户权限列表
const {role: {rights}} = reactLocalStorage.getObject('token')

const checkPagePermission = (item)=>{
  return  item.key !== "/login"  && rights.includes(item.key) && item.pagepermisson === 1
}
  

全部代码如下:

import { useEffect, useState } from "react";
import { Layout, Menu } from "antd"; 
import { matchRoutes,   useLocation,  useNavigate } from "react-router-dom";

import { routers } from "../../router"; 
import styles from "./SideMenu.module.css"; 
import siteBaseConfig from "../../config"; 
import { fetchGetMenus } from "../../utils/api"; 
import { iconList } from "./iconList";   
import {reactLocalStorage} from 'reactjs-localstorage';

const { Sider } = Layout;

export default function AppLayout() {
  const location = useLocation();
  const navigate = useNavigate(); 

  const [isInit, setIsInit] = useState(false);
  const [collapsed, setCollapsed] = useState(false); 

  // items 菜单内容	ItemType[]
  const [items, setitems] = useState([]); 
  // defaultSelectedKeys 初始选中的菜单项 key 数组
  const [defaultSelectedKeys, setDefaultSelectedKeys] = useState([]); 
  // defaultOpenKeys 初始展开的 SubMenu 菜单项 key 数组
  const [defaultOpenKeys, setDefaultOpenKeys] = useState([]);
  // submenu keys of first level
  const [rootSubmenuKeys , setrootSubmenuKeys ] = useState([]) 
  // openKeys 当前展开的 SubMenu 菜单项 key 数组
  const [openKeys, setOpenKeys] = useState([]);

  // 获取用户权限列表
  const {role: {rights}} = reactLocalStorage.getObject('token')

  const checkPagePermission = (item)=>{
    return  item.key !== "/login"  && rights.includes(item.key) &&
    item.pagepermisson === 1
  }
  
  useEffect(() => {
    async function fetchData() {
      const menusListData = await fetchGetMenus(); 
      let tempItems = [],
      rootSubmenuKeys  = [];  // submenu keys of first level
      menusListData.forEach((item) => {  
        item.key !== "/login" && rootSubmenuKeys.push(item.key) 
        checkPagePermission(item) && tempItems.push({
            label: item.title,
            key: item.key,
            icon: iconList[item.key],
            children:
              item.children &&
              item.children.length > 0 &&
              item.children.map((child) => {
                if (child.pagepermisson === 1) {
                  return {
                    label: child.title,
                    key: child.key,
                    icon: iconList[item.key],
                    children:
                      child.children &&
                      child.children.length > 0 &&
                      child.children.map((sun) => {
                        if (child.pagepermisson === 1) {
                          return {
                            label: sun.title,
                            key: sun.key,
                            icon: iconList[item.key],
                          };
                        }
                      }),
                  };
                }
              }),
          });
      });

      setitems(tempItems); 
      setrootSubmenuKeys(rootSubmenuKeys) 
    }

    fetchData();
  }, []);

  useEffect(() => {
    const routes = matchRoutes(routers, location.pathname); // 返回匹配到的路由数组对象,每一个对象都是一个路由对象
    const pathArr = [];
    if (routes !== null) {
      routes.forEach((item) => {
        const path = item.pathname;
        if (path) {
          pathArr.push(path);
        }
      });
    }
    setDefaultSelectedKeys(pathArr);
    setDefaultOpenKeys(pathArr);
    setIsInit(true);
  }, [location.pathname]);

  if (!isInit) {
    return null;
  }

  const onClick = (e) => { 
    navigate(e.key);
  };
  
  const onOpenChange = (keys) => { 
    const latestOpenKey = keys.find((key) => openKeys.indexOf(key) === -1); 
    
    if (rootSubmenuKeys.indexOf(latestOpenKey) === -1) {
      setOpenKeys(keys);
    } else {
      setOpenKeys(latestOpenKey ? [latestOpenKey] : []);
    }
  };

  return (
    <>   
      <Sider trigger={null} collapsible collapsed={collapsed}>
        <div className={styles.logo}> {siteBaseConfig.siteName} </div> 
        <Menu
          theme="dark"
          mode="inline"
          defaultSelectedKeys={defaultSelectedKeys} //  初始选中的菜单项 key 数组  string[]
          defaultOpenKeys={defaultOpenKeys} // 初始展开的 SubMenu 菜单项 key 数组
          openKeys={openKeys} // openKeys  当前展开的 SubMenu 菜单项 key 数组
          onOpenChange={onOpenChange} //onOpenChange SubMenu 展开/关闭的回调
          onClick={onClick}
          style={{
            height: "100%",
            borderRight: 0,
          }}
          items={items}
        ></Menu>
      </Sider>
    </>
  );
}

页面展示 – 根据不同用户,显示不同左侧导航

用户列表管理优化

问题所在

解决方案

优化用户列表,在真实环境中,是需要后端返回的

import { reactLocalStorage } from "reactjs-localstorage";

// 获取用户权限列表
  const { roleId, region,username } = reactLocalStorage.getObject("token");

  const roleInterface = {
    "1":"superadmin",
    "2":"regionadmin",
    "3":"regionediter", 
  }

  // 获取用户数据处理
  useEffect(() => {
    const fetchGetUserListHandle = async () => {
      const urseListData = await fetchGetUserList();

      // 注意此处
      setdataSource(roleInterface[roleId] === 'superadmin' ? urseListData: [
        ...urseListData.filter(item => item.username === username),
        ...urseListData.filter(item => item.region === region && roleInterface[item.roleId] === "regionediter"),
      ]);
    };
    fetchGetUserListHandle();
  }, []);

问题解决

全部代码

import React, { useEffect, useRef, useState } from "react";
import { Space, Switch, Table, Modal, Tag, Button } from "antd";
import {
  DeleteOutlined,
  EditOutlined,
  ExclamationCircleFilled,
} from "@ant-design/icons";

import AddUserModal from "./AddUserForm";
import UpdateUserModal from "./UpdateUserModal";

import {
  fetchGetUserList,
  fetchGetRegionList,
  fetchGetRoles,
  fetchAddUser,
  fetchDeleteUser,
  fetchPatchUser,
} from "../../utils/api";

import { reactLocalStorage } from "reactjs-localstorage";

const { confirm } = Modal;

export default function UserList() {
  const [dataSource, setdataSource] = useState([]);

  // 获取用户权限列表
  const { roleId, region,username } = reactLocalStorage.getObject("token");

  const roleInterface = {
    "1":"superadmin",
    "2":"regionadmin",
    "3":"regionediter", 
  }

  // 获取用户数据处理
  useEffect(() => {
    const fetchGetUserListHandle = async () => {
      const urseListData = await fetchGetUserList();

      // 注意此处
      setdataSource(roleInterface[roleId] === 'superadmin' ? urseListData: [
        ...urseListData.filter(item => item.username === username),
        ...urseListData.filter(item => item.region === region && roleInterface[item.roleId] === "regionediter"),
      ]);
    };
    fetchGetUserListHandle();
  }, []);

  // 添加用户处理
  const [open, setOpen] = useState(false);
  const [regionList, setregionList] = useState([]);
  const [rolesList, serolesList] = useState([]);

  const AddUserFormData = useRef(null);
  // 获取用户数据处理
  useEffect(() => {
    // 获取用户数据处理
    const fetchGetRegionListHandle = async () => {
      const urseListData = await fetchGetRegionList();
      setregionList(urseListData);
    };
    fetchGetRegionListHandle();

    // 获取用户数据处理
    const fetchGetRolesListHandle = async () => {
      const urseListData = await fetchGetRoles();
      serolesList(urseListData);
    };
    fetchGetRolesListHandle();
  }, []);

  const onCreate = async (values) => {
    console.log("Received values of form: ", values);
    setOpen(false);
    //post到后端,生成id,再设置 datasource, 方便后面的删除和更新

    const data = await fetchAddUser({
      ...values,
      roleState: true,
      default: false,
    });

    setdataSource([
      ...dataSource,
      {
        ...data,
        role: rolesList.filter((item) => item.id === values.roleId)[0],
      },
    ]);
  };

  // 删除弹框事件
  const confirmHandel = (item) => {
    confirm({
      title: "您确定要删除吗?",
      icon: <ExclamationCircleFilled />,
      content: "此处删除会删除用户,请谨慎操作!",
      okText: "确认",
      cancelText: "取消",
      onOk() {
        console.log("OK");
        deleteRolesMethod(item);
      },
      onCancel() {
        console.log("Cancel");
      },
    });
  };

  // 删除事件
  const deleteRolesMethod = async (item) => {
    console.log(item);
    setdataSource(dataSource.filter((data) => data.id != item.id));
    const data = await fetchDeleteUser(item.id);
  };

  // 用户状态修改
  const handelChange = async (item) => {
    item.roleState = !item.roleState;
    setdataSource([...dataSource]);

    // 发送请求到后端
    await fetchPatchUser(item.id, { roleState: item.roleState });
  };

  // 用户信息更改
  const [openUpdateModal, setopenUpdateModal] = useState(false);
  const UpdateUserFormData = useRef(null);
  const [isRegionDisable, setisRegionDisable] = useState(false);
  const [curentUserData, setcurentUserData] = useState(null);

  const showUserModal = (item) => {
    console.log(item);
    setopenUpdateModal(true);
    setTimeout(() => {
      if (item.roleId === 1) {
        // 禁用
        setisRegionDisable(true);
      } else {
        // 取消禁用
        setisRegionDisable(false);
      }
      UpdateUserFormData.current.setFieldsValue(item);
      setcurentUserData(item);
    }, 10);
  };

  const onUpdataUserConfirm = async (values) => {
    setopenUpdateModal(false);

    setdataSource(
      dataSource.map((item) => {
        if (item.id === curentUserData.id) {
          return {
            ...item,
            ...values,
            role: rolesList.filter((item) => item.id === values.roleId)[0],
          };
        }
        return item;
      })
    );
    setisRegionDisable(!isRegionDisable);
    // 发送请求到后端
    await fetchPatchUser(curentUserData.id, values);
  };

  const columns = [
    {
      title: "部门",
      dataIndex: "region",
      render: (region) => {
        return region === "" ? "总部" : region;
      },
      filters: [
        ...regionList.map((item) => ({
          text: item.title,
          value: item.value,
        })),
        { text: "超级管理员", value: "超级管理员" },
      ],

      // 用户删选
      onFilter: (value, record) => {
        if (value === "超级管理员") {
          return record.region === "";
        } else {
          return record.region === value;
        }
      },
    },
    {
      title: "角色名称",
      dataIndex: "role",
      render: (role) => {
        return <Tag color="magenta">{role?.roleName}</Tag>;
      },
    },

    {
      title: "用户名",
      dataIndex: "username",
    },
    {
      title: "用户可用状态",
      dataIndex: "roleState",
      render: (roleState, item) => {
        // 注意这里
        return (
          <div>
            <Switch
              size="small"
              checked={roleState}
              disabled={item.default}
              onChange={() => handelChange(item)}
            />
          </div>
        );
      },
    },
    {
      title: "操作",
      render: (item) => {
        return (
          <Space>
            <Button
              icon={<EditOutlined style={{ fontSize: "12px" }} />}
              shape="circle"
              type="primary"
              size="small"
              onClick={() => showUserModal(item)}
            ></Button>
            <Button
              icon={<DeleteOutlined style={{ fontSize: "12px" }} />}
              shape="circle"
              danger
              size="small"
              onClick={() => {
                confirmHandel(item);
              }}
            ></Button>
          </Space>
        );
      },
    },
  ];

  return (
    <div>
      <div style={{ padding: "0 0 20px 0" }}>
        <Button
          size="small"
          type="primary"
          onClick={() => {
            setOpen(true);
            console.log(AddUserFormData);
          }}
        >
          添加用户
        </Button>
      </div>
      <Table
        dataSource={dataSource}
        columns={columns}
        rowKey={(item) => item.id}
        pagination={{
          pageSize: 5,
        }}
      ></Table>
      <AddUserModal
        open={open}
        onCreate={onCreate}
        onCancel={() => {
          setOpen(false);
        }}
        regionList={regionList}
        rolesList={rolesList}
        ref={AddUserFormData}
      ></AddUserModal>

      <UpdateUserModal
        open={openUpdateModal}
        onUpdataUserConfirm={onUpdataUserConfirm}
        onUpdataUserCancel={() => {
          setopenUpdateModal(false);
        }}
        regionList={regionList}
        rolesList={rolesList}
        ref={UpdateUserFormData}
        isRegionDisable={isRegionDisable}
      ></UpdateUserModal>
    </div>
  );
}
  • 1
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论
好的,下面是一个简单的示例代码: 首先安装所需依赖: ```bash npm install react antd axios --save ``` 然后创建一个 `Login` 组件和一个 `Register` 组件,分别用于登录和注册的页面展示。其中,我们使用了 `Form` 和 `Input` 组件来实现表单输入,以及 `Button` 组件来实现按钮的功能。 ```jsx // Login.jsx import React, { useState } from 'react'; import { Form, Input, Button, message } from 'antd'; import axios from 'axios'; const Login = () => { const [loading, setLoading] = useState(false); const onFinish = async (values) => { setLoading(true); try { const response = await axios.post('/api/login', values); message.success(response.data.message); setLoading(false); } catch (error) { message.error(error.response.data.message); setLoading(false); } }; return ( <Form name="login" onFinish={onFinish}> <Form.Item name="email" rules={[{ required: true, message: 'Please input your email!' }]} > <Input placeholder="Email" /> </Form.Item> <Form.Item name="password" rules={[{ required: true, message: 'Please input your password!' }]} > <Input.Password placeholder="Password" /> </Form.Item> <Form.Item> <Button type="primary" htmlType="submit" loading={loading}> Login </Button> </Form.Item> </Form> ); }; export default Login; ``` ```jsx // Register.jsx import React, { useState } from 'react'; import { Form, Input, Button, message } from 'antd'; import axios from 'axios'; const Register = () => { const [loading, setLoading] = useState(false); const onFinish = async (values) => { setLoading(true); try { const response = await axios.post('/api/register', values); message.success(response.data.message); setLoading(false); } catch (error) { message.error(error.response.data.message); setLoading(false); } }; return ( <Form name="register" onFinish={onFinish}> <Form.Item name="name" rules={[{ required: true, message: 'Please input your name!' }]} > <Input placeholder="Name" /> </Form.Item> <Form.Item name="email" rules={[{ required: true, message: 'Please input your email!' }]} > <Input placeholder="Email" /> </Form.Item> <Form.Item name="password" rules={[{ required: true, message: 'Please input your password!' }]} > <Input.Password placeholder="Password" /> </Form.Item> <Form.Item name="confirmPassword" rules={[ { required: true, message: 'Please confirm your password!' }, ({ getFieldValue }) => ({ validator(_, value) { if (!value || getFieldValue('password') === value) { return Promise.resolve(); } return Promise.reject(new Error('The two passwords do not match!')); }, }), ]} > <Input.Password placeholder="Confirm Password" /> </Form.Item> <Form.Item> <Button type="primary" htmlType="submit" loading={loading}> Register </Button> </Form.Item> </Form> ); }; export default Register; ``` 在这里我们使用了 `axios` 来发送登录和注册的请求,需要注意的是,这里的请求地址是 `/api/login` 和 `/api/register`,而不是实际的后端接口地址,根据实际情况进行修改。 最后在需要展示登录和注册功能的页面中引入 `Login` 和 `Register` 组件即可。 ```jsx import React from 'react'; import Login from './Login'; import Register from './Register'; const LoginPage = () => { return ( <div> <h1>Login</h1> <Login /> <hr /> <h1>Register</h1> <Register /> </div> ); }; export default LoginPage; ``` 以上就是一个简单的使用 `react` 和 `antd` 实现登录和注册功能的示例代码,希望对你有帮助。
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

前端布道人

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值