目前我们的功能只是实现了调用登录接口,并返回数据. 针对接下来的逻辑需要继续完善.
暂存用户信息和token
思路和 vue-element-admin 基本类似: 登录返回token,存储token, 根据是否有token判断是否允许访问后台页面.
和之前的项目一样, 创建工具类 utils/myAuth.ts :
import type {UserInfoType} from '@/pages/Login/model';// 定义keyconst USER_INFO_KEY = 'REACT_DEMO_USER';const USER_TOKEN_KEY = 'REACT_DEMO_TOKEN';/*** 把用户信息存入localstorage* @param userInfo*/export function saveUserInfo(userInfo: UserInfoType){const userInfoStr: string = JSON.stringify(userInfo);window.localStorage.setItem(USER_INFO_KEY,userInfoStr);}/*** 删除用户信息*/export function removeUserInfo(){window.localStorage.removeItem(USER_INFO_KEY);}/*** 获取用户信息*/export function getUserInfo(){const userInfoStr = window.localStorage.getItem(USER_INFO_KEY);if(userInfoStr == null){return null;}const userInfo: UserInfoType = JSON.parse(userInfoStr);return userInfo;}/*** 保存token* @param token*/export function saveToken(token: string){window.localStorage.setItem(USER_TOKEN_KEY,token);}/*** 删除token*/export function removeToken(){window.localStorage.removeItem(USER_TOKEN_KEY);}/*** 获取token*/export function getToken(){const tokenStr = window.localStorage.getItem(USER_TOKEN_KEY);return tokenStr;}/*** 清楚用户登录信息*/export function clearUserLoginInfo(){removeUserInfo();removeToken();}
因为具体的登录调用和结果处理是在 pages/Login/model.ts 中的,所以在这里把用户信息和token存入localstorage:
import {saveUserInfo,saveToken } from '@/utils/myAuth';...*doLogin(action, {call,put}){// 打印action,查看对象结构// console.log(action);const {payload} = action;// message与引入模块冲突 重命名为: errMsgconst {data,success,message:errMsg} = yield call(loginApi,payload);if(success){// 存入state// put专门用于调用 reducer, 而且需要添加 迭代器 yield修饰符yield put({type: 'initUserInfo',payload: data})// 存入localstorage// 思考: 为什么不用state?const {token,userInfo} = data;saveUserInfo(userInfo);saveToken(token);message.success('登录成功!');// 跳转后台页面window.location.href = '/';}else{// 返回错误 提示错误信息 不跳转message.error(errMsg);}
导航守卫?
但是在antd中没有vue中的导航守卫, 我们需要在 src\layouts\SecurityLayout.tsx 中对是否登录做权限控制.
通过自定义 isLogin 的逻辑, 我们就可以判断是否允许访问位于 SecurityLayout 内部的组件. 实际开发中,后台除了登录页(排除个别声明页)其他页面都应该在登录权限的保护下访问. 所以 SecurityLayout 组件可以理解为antd项目的 导航守卫 .
注意: 至于页面权限,比如用户角色能否访问某个页面的需求,暂且不讨论,我们会后期在权限篇给大家说明.
import React from 'react';import { PageLoading } from '@ant-design/pro-layout';import type { ConnectProps } from 'umi';import { Redirect, connect } from 'umi';import { stringify } from 'querystring';// import type { ConnectState } from '@/models/connect';import type { CurrentUser } from '@/models/user';import {getToken} from '@/utils/myAuth';type SecurityLayoutProps = {loading?: boolean;currentUser?: CurrentUser;} & ConnectProps;type SecurityLayoutState = {isReady: boolean;};class SecurityLayout extends React.Component<SecurityLayoutProps, SecurityLayoutState> {state: SecurityLayoutState = {isReady: false,};// react的声明周期函数 组件渲染完毕后触发,类似于vue的mountedcomponentDidMount() {this.setState({isReady: true,});// 这里注销掉 已经不需要框架本身的 登录 方法// const { dispatch } = this.props;// if (dispatch) {// dispatch({// type: 'user/fetchCurrent',// });// }}render() {const { isReady } = this.state;const { children, loading } = this.props;// You can replace it to your authentication rule (such as check token exists)// 你可以把它替换成你自己的登录认证规则(比如判断 token 是否存在)// const isLogin = currentUser && currentUser.userid;// 这里换成判断token是否存在const isLogin = getToken();const queryString = stringify({redirect: window.location.href,});if ((!isLogin && loading) || !isReady) {return <PageLoading/>;}if (!isLogin && window.location.pathname !== '/user/login') {return <Redirect to={`/user/login?${queryString}`} />;}return children;}}// #https://github.com/dvajs/dva/tree/master/packages/dva-loading// # https://umijs.org/zh-CN/plugins/plugin-dva// 这里 loading是dva-loading 的插件,可以监听某个model的effects是否出于异步进行中const mapStateToProps = (state: any) =>{return {loading: state.loading.models.myLogin as boolean}}export default connect(mapStateToProps)(SecurityLayout);
登录成功!
我们成功的登录到了后台,进入默认欢迎页,但页发现了几个问题:
- header的用户中心为什么是loading状态?
- 左边导航应该有的admin(权限)页面跑哪了?
- 因为我们使用了自己的登录逻辑,并没有标识当前用户的角色,导航菜单是根据角色判断的
- 如何退出?
退出
剩下两个问题很容易通过阅读 src\components\GlobalHeader\AvatarDropdown.tsx 代码来理解.
退出功能就比较简单了,我们需要清空用户登录信息和token,然后跳转首页:
import { LogoutOutlined, SettingOutlined, UserOutlined } from '@ant-design/icons';import { Avatar, Menu, Spin } from 'antd';import type { Dispatch } from 'react';import React from 'react';import type { ConnectProps } from 'umi';import { history ,connect} from 'umi';// import type { ConnectState } from '@/models/connect';import type { CurrentUser } from '@/models/user';// 引入封装的获取用户信息的方法import {getUserInfo} from '@/utils/myAuth';import HeaderDropdown from '../HeaderDropdown';import styles from './index.less';export type GlobalHeaderRightProps = {currentUser?: CurrentUser;menu?: boolean;} & Partial<ConnectProps>;class AvatarDropdown extends React.Component<GlobalHeaderRightProps> {onMenuClick = (event: {key: React.Key;keyPath: React.Key[];item: React.ReactInstance;domEvent: React.MouseEvent<HTMLElement>;}) => {const { key } = event;// 判断如果是退出按钮 则执行退出逻辑if (key === 'logout') {const { dispatch } = this.props;if (dispatch) {dispatch({type: 'myLogin/doLogout',});}return;}history.push(`/account/${key}`);};render(): React.ReactNode {const { menu} = this.props;// 根据自己的业务逻辑 从localStorage中获取用户信息const currentUser = getUserInfo();const menuHeaderDropdown = (<Menu className={styles.menu} selectedKeys={[]} onClick={this.onMenuClick}>{menu && (<Menu.Item key="center"><UserOutlined />个人中心</Menu.Item>)}{menu && (<Menu.Item key="settings"><SettingOutlined />个人设置</Menu.Item>)}{menu && <Menu.Divider />}<Menu.Item key="logout"><LogoutOutlined />退出登录</Menu.Item></Menu>);return currentUser && currentUser.username ? (<HeaderDropdown overlay={menuHeaderDropdown}><span className={`${styles.action} ${styles.account}`}><Avatar size="small" className={styles.avatar} src={currentUser.icon} alt="avatar" /><span className={`${styles.name} anticon`}>{currentUser.nickname}</span></span></HeaderDropdown>) : (<span className={`${styles.action} ${styles.account}`}><Spinsize="small"style={{marginLeft: 8,marginRight: 8,}}/></span>);}}// 没有用到mode 所以这里暂时注销// export default connect(({ user }: ConnectState) => ({// currentUser: user.currentUser,// }))(AvatarDropdown);const mapDispatchToProps = (dispatch)=>({dispatch})export default connect(mapDispatchToProps)(AvatarDropdown);
修改 pages/Login/model.ts :
import { history } from 'umi';import {clearUserLoginInfo} from '@/utils/myAuth';export type MType = {...effects: {doLogin: Effect;doLogout: Effect;};...}effects: {...// 添加doLogout方法doLogout(){// 清空登录用户信息和tokenclearUserLoginInfo();history.replace({pathname: '/user/login'});}}
总结
在不考虑菜单访问权限的前提下,我们已经实现了基本的登录与退出逻辑,而且获取到了用户的基本信息和之后访问api所需要的token . 我们可以快乐的开始接下来的开发了.
