As an internal system of an enterprise, users are not allowed to access any page content in the system unless they have logged in.
So what to achieve in this section:
- Implement a login page. When the user is not logged in, he can only see the content of this login page when accessing the system
- Judging roles and permissions for previous content
In addition, in order to avoid the login status reset after refreshing the page, the user’s login information should also be saved.
This requires the browser’s localstorage feature.
This article just provides an idea, specifically, you can modify it according to the project.
Dropdown menu
How to trigger events?
After clicking the menu item, an event will be triggered, and the user can perform different operations through the corresponding menu item key
.
import { DownOutlined } from '@ant-design/icons'; import { Dropdown, message, Space } from 'antd'; // notice here 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, // notice here }} > <a onClick={(e) => e. preventDefault()}> <Space> Hover me, Click menu item <DownOutlined /> </Space> </a> </dropdown> ); export default App;
Skip to login
Implementation code
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>
Layout login page
login box
Ordinary login box, can accommodate more elements.
Implementation code
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>Background system</h2> <Form name="normal_login" className="login-form" onFinish={onFinish}> <Form.Item name="username" rules={[ { required: true, message: "Please enter a username!", }, ]} > <Input prefix={<UserOutlined className="site-form-item-icon" />} placeholder="Please enter a username" /> </Form.Item> <Form.Item name="password" rules={[ { required: true, message: "Please enter your password", }, ]} > <Input prefix={<LockOutlined className="site-form-item-icon" />} type="password" placeholder="Please enter a password" /> </Form.Item> <Form.Item> <Space size="large" style={<!-- -->{width: '100%',justifyContent: 'center'}}> <Button type="primary" htmlType="submit" className="login-form-button" > Log in </Button> <a href="#" style={<!-- -->{color: '#fff'}}>Register</a> </Space> </Form.Item> </Form> </div> </div> ); }
Page display
Login verification
reactjs-localstorage react stores local data, download address
Install
npm install reactjs-localstorage or yarn add reactjs-localstorage
Use Case
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 is limited, so temporarily use the get request. But In the real environment, the backend must judge whether you can log in.
Implementation code
import {reactLocalStorage} from 'reactjs-localstorage'; //Introduce component 'reactjs-localstorage' const onFinish = async (values) => { let userData = await fetchGetUsersLogin(values) // Determine whether it is an available user if(userData. length > 0){ reactLocalStorage.setObject('token', userData[0]); navigate('/home'); } else { message.error(`Login failed`); } };
Page display
In practice, the token returned by the backend is often a large string of strings, not real user data.
All code
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) // Determine whether it is an available user if(userData. length > 0){ delete userData[0].password reactLocalStorage.setObject('token', userData[0]); navigate('/home'); } else { message.error(`Login failed`); } }; return ( <div className={styles["login-page-container"]} style={<!-- -->{}}> <i className={styles["mask"]}></i> <div className={styles["login-wrapper"]}> <h2>Background system</h2> <Form name="normal_login" className="login-form" onFinish={onFinish}> <Form.Item name="username" rules={[ { required: true, message: "Please enter a username!", }, ]} > <Input prefix={<UserOutlined className="site-form-item-icon" />} placeholder="Please enter a username" /> </Form.Item> <Form.Item name="password" rules={[ { required: true, message: "Please enter your password", }, ]} > <Input prefix={<LockOutlined className="site-form-item-icon" />} type="password" placeholder="Please enter a password" /> </Form.Item> <Form.Item> <Space size="large" style={<!-- -->{ width: "100%", justifyContent: "center" }} > <Button type="primary" htmlType="submit" className="login-form-button" > Log in </Button> <a href="#" style={<!-- -->{ color: "#fff" }}> register </a> </Space> </Form.Item> </Form> </div> </div> ); }
Permission check
In this part, the previous left navigation component needs to be modified: the content of the left navigation needs to be dynamically displayed according to the user’s permissions.
See the content of the left navigation component for details
Modify the previous components, add user permission judgment
import {reactLocalStorage} from 'reactjs-localstorage'; // Get the list of user permissions const {role: {rights}} = reactLocalStorage. getObject('token') const checkPagePermission = (item) => { return item.key !== "/login" & amp; & amp; rights. includes(item.key) & amp; & amp; item.pagepermission === 1 }
The whole code is as follows:
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 menu content ItemType[] const [items, setitems] = useState([]); // defaultSelectedKeys Initially selected menu item key array const [defaultSelectedKeys, setDefaultSelectedKeys] = useState([]); // defaultOpenKeys Initially expanded SubMenu menu item key array const [defaultOpenKeys, setDefaultOpenKeys] = useState([]); // submenu keys of first level const [rootSubmenuKeys , setrootSubmenuKeys ] = useState([]) // openKeys currently expanded SubMenu menu item key array const [openKeys, setOpenKeys] = useState([]); // Get the list of user permissions const {role: {rights}} = reactLocalStorage. getObject('token') const checkPagePermission = (item) => { return item.key !== "/login" & amp; & amp; rights. includes(item.key) & amp; & amp; item.pagepermission === 1 } useEffect(() => { async function fetchData() { const menusListData = await fetchGetMenus(); let tempItems = [], rootSubmenuKeys = []; // submenu keys of first level menusListData. forEach((item) => { item.key !== "/login" & amp; & amp; rootSubmenuKeys.push(item.key) checkPagePermission(item) & amp; & amp; 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. pagepermission === 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. pagepermission === 1) { return { label: sun.title, key: sun.key, icon: iconList[item.key], }; } }), }; } }), }); }); setitems(tempItems); setrootSubmenuKeys(rootSubmenuKeys) } fetchData(); }, []); useEffect(() => { const routes = matchRoutes(routers, location.pathname); // Return the matched route array objects, each object is a route object 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} // Initially selected menu item key array string[] defaultOpenKeys={defaultOpenKeys} // Initially expanded SubMenu menu item key array openKeys={openKeys} // openKeys currently expanded SubMenu menu item key array onOpenChange={onOpenChange} //onOpenChange SubMenu expand/close callback onClick={onClick} style={<!-- -->{ height: "100%", borderRight: 0, }} items={items} ></Menu> </Sider> </> ); }
Page display – According to different users, display different left navigation
User list management optimization
What is the problem?
Solution
To optimize the user list, in a real environment, it needs to be returned by the backend.
import { reactLocalStorage } from "reactjs-localstorage"; // Get the list of user permissions const { roleId, region, username } = reactLocalStorage. getObject("token"); const roleInterface = { "1": "superadmin", "2": "regionadmin", "3": "regionediter", } // Get user data processing useEffect(() => { const fetchGetUserListHandle = async () => { const urseListData = await fetchGetUserList(); // notice here setdataSource(roleInterface[roleId] === 'superadmin' ? urseListData: [ ...urseListData.filter(item => item.username === username), ...urseListData.filter(item => item.region === region & amp; & amp; roleInterface[item.roleId] === "regionediter"), ]); }; fetchGetUserListHandle(); }, []);
Problem resolution
All code
import React, { useEffect, useRef, useState } from "react"; import { Space, Switch, Table, Modal, Tag, Button } from "antd"; import { Delete Outlined, 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([]); // Get the list of user permissions const { roleId, region, username } = reactLocalStorage. getObject("token"); const roleInterface = { "1": "superadmin", "2": "regionadmin", "3": "regionediter", } // Get user data processing useEffect(() => { const fetchGetUserListHandle = async () => { const urseListData = await fetchGetUserList(); // notice here setdataSource(roleInterface[roleId] === 'superadmin' ? urseListData: [ ...urseListData.filter(item => item.username === username), ...urseListData.filter(item => item.region === region & amp; & amp; roleInterface[item.roleId] === "regionediter"), ]); }; fetchGetUserListHandle(); }, []); // add user handler const [open, setOpen] = useState(false); const [regionList, setregionList] = useState([]); const [rolesList, serolesList] = useState([]); const AddUserFormData = useRef(null); // Get user data processing useEffect(() => { // Get user data processing const fetchGetRegionListHandle = async () => { const urseListData = await fetchGetRegionList(); setregionList(urseListData); }; fetchGetRegionListHandle(); // Get user data processing const fetchGetRolesListHandle = async () => { const urseListData = await fetchGetRoles(); serolesList(urseListData); }; fetchGetRolesListHandle(); }, []); const onCreate = async (values) => { console.log("Received values of form: ", values); setOpen(false); //post to the backend, generate an id, and then set the datasource to facilitate subsequent deletion and update const data = await fetchAddUser({ ...values, roleState: true, default: false, }); setdataSource([ ...dataSource, { ...data, role: rolesList.filter((item) => item.id === values.roleId)[0], }, ]); }; // Delete popup event const confirmHandel = (item) => { confirm({ title: "Are you sure you want to delete?", icon: <ExclamationCircleFilled />, content: "Deleting here will delete the user, please be careful!", okText: "Confirm", cancelText: "Cancel", onOk() { console.log("OK"); deleteRolesMethod(item); }, onCancel() { console.log("Cancel"); }, }); }; // delete event const deleteRolesMethod = async (item) => { console. log(item); setdataSource(dataSource. filter((data) => data. id != item. id)); const data = await fetchDeleteUser(item.id); }; // user state modification const handelChange = async (item) => { item.roleState = !item.roleState; setdataSource([...dataSource]); // send request to backend await fetchPatchUser(item.id, { roleState: item.roleState }); }; // change user information 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) { // disable setisRegionDisable(true); } else { // cancel disable 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); // send request to backend await fetchPatchUser(curentUserData.id, values); }; const columns = [ { title: "Department", dataIndex: "region", render: (region) => { return region === "" ? "Headquarters" : region; }, filters: [ ...regionList. map((item) => ({ text: item. title, value: item. value, })), { text: "Super Admin", value: "Super Admin" }, ], // user delete onFilter: (value, record) => { if (value === "super administrator") { return record. region === ""; } else { return record. region === value; } }, }, { title: "Role Name", dataIndex: "role", render: (role) => { return <Tag color="magenta">{role?.roleName}</Tag>; }, }, { title: "Username", dataIndex: "username", }, { title: "User Available Status", dataIndex: "roleState", render: (roleState, item) => { // notice here return ( <div> <Switch size="small" checked={roleState} disabled={item.default} onChange={() => handelChange(item)} /> </div> ); }, }, { title: "Operation", 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); }} > Add user </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> ); }