一.思路:
1.首先要有登录页面
2.app.tsx 里面,如果没有登录,要跳转到登录页
3.utils里面:request.tsx(异常处理,请求拦截) ,index.ts(storage,存储在loaclstorage里面)
二.具体步骤:
1.登录页面
import { LockOutlined, UserOutlined } from '@ant-design/icons';
import { Alert, message } from 'antd';
import React, { useState } from 'react';
import ProForm, { ProFormCheckbox, ProFormText } from '@ant-design/pro-form';
import { useIntl, Link, history, FormattedMessage, SelectLang, useModel } from 'umi';
import { storage } from '@/utils';
import { login } from '@/services/user';
import authRouter from '@/utils/authRouter';
import styles from './index.less';
const LoginMessage: React.FC<{
content: string;
}> = ({ content }) => (
<Alert
style={{
marginBottom: 24,
}}
message={content}
type="error"
showIcon
/>
);
/** 此方法会跳转到 redirect 参数所在的位置 */
const goto = () => {
if (!history) return;
setTimeout(() => {
const { query } = history.location; //如果ts报错,找不到query,就删掉行,直接跳转
const { redirect } = query as { redirect: string };
history.push(redirect || '/');
}, 10);
};
const Login: React.FC = () => {
const [submitting, setSubmitting] = useState(false);
const [userLoginState, setUserLoginState] = useState<API.LoginResult>({});
const { initialState, setInitialState } = useModel('@@initialState');
const intl = useIntl();
const handleSubmit = async (values: API.LoginParams) => {
setSubmitting(true);
try {
// 登录
const { results } = await login({ ...values });
if (results.token) {
const defaultloginSuccessMessage = intl.formatMessage({
id: 'pages.login.success',
defaultMessage: '登录成功!',
});
console.log(results);
message.success(defaultloginSuccessMessage);
setInitialState({ //初始信息
...initialState,
currentUser: results,
token: results.token,
hasRouter: authRouter(results.access_data), //不同权限,侧边栏把不同
});
storage.set({ name: 'token', data: results.token }); //存储token
storage.set({ name: 'userInfo', data: JSON.stringify(results) });
storage.set({ name: 'hasRouter', data: JSON.stringify(authRouter(results.access_data)) });
goto();
return;
}
// 如果失败去设置用户错误信息
setUserLoginState({ status: 'error' });
} catch (error) {
const defaultloginFailureMessage = intl.formatMessage({
id: 'pages.login.failure',
defaultMessage: '登录失败,请重试!',
});
message.error(defaultloginFailureMessage);
}
setSubmitting(false);
};
const { status, type: loginType } = userLoginState;
return (
<div className={styles.container}>
<div className={styles.content}>
<div className={styles.main}>
<div className={styles.header}>
</div>
<div className={styles.form}>
<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);
}}
>
{status === 'error' && loginType === 'account' && (
<LoginMessage
content={intl.formatMessage({
id: 'pages.login.accountLogin.errorMessage',
defaultMessage: '账户或密码错误(admin/ant.design)',
})}
/>
)}
<>
<ProFormText
name="username"
fieldProps={{
size: 'large',
prefix: <UserOutlined className={styles.prefixIcon} />,
}}
placeholder={intl.formatMessage({
id: 'pages.login.username.placeholder',
})}
rules={[
{
required: true,
message: <FormattedMessage id="pages.login.username.required" />,
},
]}
/>
<ProFormText.Password
name="password"
fieldProps={{
size: 'large',
prefix: <LockOutlined className={styles.prefixIcon} />,
}}
placeholder={intl.formatMessage({
id: 'pages.login.password.placeholder',
})}
rules={[
{
required: true,
message: <FormattedMessage id="pages.login.password.required" />,
},
]}
extra={
<div style={{ textAlign: 'center', paddingTop: 6 }}>
<FormattedMessage
id="pages.login.forgotPassword"
defaultMessage="如忘记密码,请联系您的管理员进行重置"
/>
</div>
}
/>
</>
{status === 'error' && loginType === 'mobile' && (
<LoginMessage content="验证码错误" />
)}
<div className={styles.loginButtons}>
<ProFormCheckbox noStyle>
<FormattedMessage id="pages.login.rememberUser" defaultMessage="记住账号" />
</ProFormCheckbox>
<ProFormCheckbox noStyle>
<FormattedMessage id="pages.login.rememberPassword" defaultMessage="记住密码" />
</ProFormCheckbox>
<ProFormCheckbox noStyle>
<FormattedMessage id="pages.login.rememberMe" defaultMessage="自动登录" />
</ProFormCheckbox>
</div>
</ProForm>
</div>
</div>
</div>
</div>
);
};
export default Login;
2.request.ts
这是umi3的处理。umi4官网已经改版,具体可以查看官网。我这里是提供另外一种改法。
import { notification, message, Modal } from 'antd';
import { storage } from '@/utils';
import config from './config';
import { history } from 'umi';
import { RequestOptionsInit } from 'umi-request';
const codeMessage = {
200: '服务器成功返回请求的数据。',
201: '新建或修改数据成功。',
202: '一个请求已经进入后台排队(异步任务)。',
204: '删除数据成功。',
400: '发出的请求有错误,服务器没有进行新建或修改数据的操作。',
401: '用户没有权限(令牌、用户名、密码错误)。',
403: '用户得到授权,但是访问是被禁止的。',
404: '发出的请求针对的是不存在的记录,服务器没有进行操作。',
405: '请求方法不被允许。',
406: '请求的格式不可得。',
410: '请求的资源被永久删除,且不会再得到的。',
422: '当创建一个对象时,发生一个验证错误。',
500: '服务器发生错误,请检查服务器。',
502: '网关错误。',
503: '服务不可用,服务器暂时过载或维护。',
504: '网关超时。',
};
let isTokenOverdue = false;
export default {
prefix: config.host,
timeout: 60000,
headers: {
'Content-Type': 'application/json; charset=utf-8',
},
errorHandler: (error: { response: Response }): Response => {
const { response } = error;
if (response && response.status) {
const errorText = codeMessage[response.status] || response.statusText;
const { status, url } = response;
notification.error({
message: `请求错误 ${status}: ${url}`,
description: errorText,
});
} else if (!response) {
notification.error({
description: '您的网络发生异常,无法连接服务器',
message: '网络异常',
});
}
return response;
},
errorConfig: {
adaptor: (resData: any) => {
return {
...resData,
code: resData.status_code,
};
},
},
middlewares: [
async function middlewareA(ctx: any, next: any) {
await next();
const { res } = ctx;
if (res.status_code !== 200) {
switch (res.status_code) {
case 401:
if (!isTokenOverdue) {
Modal.error({
title: '警告',
content: '您的登录信息已过期,请重新登录!!!',
onOk: () => {
history.replace({ pathname: '/welcome', query: { key: 'logout' } });
},
});
isTokenOverdue = true;
}
break;
default:
message.error(res.msg);
return false;
}
}
return ctx;
},
],
requestInterceptors: [
async function authHeaderInterceptor(url: string, options: RequestOptionsInit) {
const authHeader = {
Authorization: storage.get('token') ? `Token ${storage.get('token')}` : '',
'YT-LANGUAGE': JSON.parse(storage.get('userInfo') as any)?.user_language || '',
};
return {
url: `${url}`,
options: { ...options, interceptors: true, headers: { ...options.headers, ...authHeader } },
};
},
],
};
新改法:
import { message, notification } from 'antd'
import { extend } from 'umi-request'
const codeMessage = {
200: '服务器成功返回请求的数据。',
201: '新建或修改数据成功。',
202: '一个请求已经进入后台排队(异步任务)。',
204: '删除数据成功。',
400: '发出的请求有错误,服务器没有进行新建或修改数据的操作。',
401: '登录过期,请重新登陆。',
403: '用户得到授权,但是访问是被禁止的。',
404: '发出的请求针对的是不存在的记录,服务器没有进行操作。',
406: '请求的格式不可得。',
410: '请求的资源被永久删除,且不会再得到的。',
422: '当创建一个对象时,发生一个验证错误。',
500: '服务器发生错误,请检查服务器。',
502: '网关错误。',
503: '服务不可用,服务器暂时过载或维护。',
504: '网关超时。',
}
/**
* 异常处理程序
*/
const errorHandler = (error: { response: Response }): Response => {
const { response } = error
if (response && response.status) {
const errorText = codeMessage[response.status] || response.statusText
const { status, url } = response
message.error(errorText)
notification.error({
message: `请求错误 ${status}: ${url}`,
description: errorText,
})
} else if (!response) {
// message.error('您的网络发生异常,无法连接服务器')
notification.error({
description: '您的网络发生异常,无法连接服务器',
message: '网络异常',
})
}
return response
}
/**
* 配置request请求时的默认参数
*/
const request = extend({
errorHandler, // 默认错误处理
credentials: 'include', // 默认请求是否带上cookie
})
export default request
utils.ts
import moment from 'moment';
export const storage = {
get: (name: string) => {
return localStorage.getItem(name);
},
set: ({ name, data }: { name: string; data: string }) => {
localStorage.setItem(name, data);
},
clearItem: (name: string) => {
localStorage.removeItem(name);
},
clear: () => {
localStorage.clear();
},
};
export const formatTime = (time: string) => {
return moment(time).format('YYYY-MM-DD HH:mm:ss');
};