项目 1 我们直接上代码
App 文件
import React from 'react';
import { Provider } from 'mobx-react';
import store from '@/store/index';
import ElLayout from '@/layout';
import MultiTabMobx from '@/store/multiTab';
import './style/index.less';
import {
BrowserRouter as Router,
Route,
Redirect,
Switch
} from 'react-router-dom';
import { isLogin } from '@/utils/utils';
import Login from '@/page/Login/index';
import Err404 from '@/page/errPage/Err404';
class App extends React.Component<any> {
componentWillMount() {
store.getPrincipal();
}
componentDidMount() {
//在页面刷新时将mobx里的信息保存到sessionStorage里
window.addEventListener('beforeunload', () => {
store.setPrincipal();
});
}
render() {
return (
<Router>
<Provider store={store} multiTabMobx={MultiTabMobx}>
{/* <ElLayout /> */}
<Switch>
<Route path='/login' component={Login} /> // 登录页面
<Route path='/404' component={Err404} />
<Route
path='/'
render={() =>
!!isLogin() ? <ElLayout /> : <Redirect to='/login' />
}
/>
{/* <Route path='*' component={Err404} /> */}
</Switch>
</Provider>
</Router>
);
}
}
export default App;
那么我们的isLogin在 utils文件下
import { message } from 'antd';
import { pathToRegexp } from 'path-to-regexp';
import route from '@/config'; // 路由配置指向文件引入,自己写的路由地址文件
/**
* @name isIE
* @description 判断是否是IE
*/
export function isIE() {
const bw = window.navigator.userAgent;
const compare = (s) => bw.indexOf(s) >= 0;
const ie11 = (() => 'ActiveXObject' in window)();
return compare('MSIE') || ie11;
}
/**
* @name isLogin
* @description 判断是否登录
*/
export function isLogin() {
return localStorage.getItem('Authorization');
}
/**
* @interface exportParam
* @description 导出
*/
interface exportParam {
url: string;
params?: Object;
fileName: string;
timeOut?: Number;
method?: 'POST' | 'GET';
}
/**
* @name requestExport
* @description 发送请求
* @param url
* @param params
* @param method
* @param signal
* @param controller
* @returns
*/
function requestExport(url, params, method, signal, controller) {
function getRouteMap() {
const storageItemName = `el_routemaps_${process.env.REACT_APP_DOMAIN_NAME}`
let routemap = JSON.parse(sessionStorage.getItem(storageItemName));
if (routemap) {
return routemap;
} else {
routemap = new Object();
function generateRoute(route) {
route.map((v) => {
if (v.path) {
routemap[v.name] = v.path;
}
if (v.routes) {
generateRoute(v.routes);
}
});
}
generateRoute(route);
sessionStorage.setItem(storageItemName, JSON.stringify(routemap));
return routemap;
}
}
return new Promise((resolve) => {
const routemap = getRouteMap();
let routeKey = '';
for (let i in routemap) {
if (pathToRegexp(routemap[i]).test(window.location.pathname)) {
routeKey = i;
}
}
let options: any = {
method: method,
headers: new Headers({
'Content-Type': 'application/json;application/octet-stream',
Authorization: `${window.localStorage.getItem('Authorization')}`,
RouteKey: routeKey
}),
signal: signal
};
if (method === 'POST') {
options.body = JSON.stringify(params);
}
console.log('options', options);
fetch(url, { ...options })
.then((response) => {
const { ok } = response;
if (!ok) {
message.error('网络错误');
controller.abort();
throw '网络错误';
}
return response;
})
.then((res) => res.blob())
.catch((error) => {
throw error;
})
.then((response) => {
resolve(response);
});
});
}
/**
* @name timeoutPromise
* @description 超时处理
* @param timeout
* @param controller
* @returns
*/
export function timeoutPromise(timeout, controller) {
return new Promise((resolve, reject) => {
setTimeout(() => {
reject(new Response('timeout', { status: 521, statusText: 'timeout ' }));
//超时自动关闭当前请求
controller.abort();
}, timeout);
});
}
/**
* @name download
* @description 下载导出文件
* @param blobs
* @param fileName
*/
function download(blobs, fileName) {
const defaultName = '未命名的导出文件';
const blob = new Blob([blobs]);
const name = `${fileName ? fileName : defaultName}.xlsx`;
if ('download' in document.createElement('a')) {
// 非IE下载
const elink = document.createElement('a');
elink.download = name;
elink.style.display = 'none';
elink.href = URL.createObjectURL(blob);
document.body.appendChild(elink);
elink.click();
URL.revokeObjectURL(elink.href); // 释放URL 对象
document.body.removeChild(elink);
} else {
// IE10+下载
navigator.msSaveBlob(blob, fileName);
}
}
/**
* @name commonExport
* @description 公共导出
* @param param0
* @returns
*/
export function commonExport({
url,
params,
method = 'POST',
fileName,
timeOut
}: exportParam) {
let controller = new AbortController();
let signal = controller.signal;
let time = timeOut ? timeOut : 60000;
return Promise.race([
requestExport(url, params, method, signal, controller),
timeoutPromise(time, controller)
])
.then((resp: any) => {
download(resp, fileName);
})
.catch((err) => {
throw err + '请求超时';
});
}
/**
* @name enCodeStr
* @description 前端字符串加密
* @param str
* @returns
*/
export const enCodeStr = (str: string): string => {
return btoa(str);
};
/**
* @name enCodeStr
* @description 前端字符串解密
* @param str
* @returns
*/
export const deCodeStr = (str: string): string => {
return atob(str);
};
那么我们登录页面 Login
import React from 'react';
import { Tabs, Form, Input, Button } from 'antd';
import { LockBlack, UserBlack, LoadingBlack } from '@/components/el/ElIcon';
import { observer, inject } from 'mobx-react';
import * as service from './service';
import './style.less';
import { ElNotification } from '@/components/el';
import { enCodeStr } from '@/utils/utils'; // 前端字符串加密
const { TabPane } = Tabs;
interface State {
loading: boolean;
captchaLoading: boolean;
isShow: boolean;
answer: string;
captcha: { [props: string]: any };
redirectUrl: string;
openEnvironmentDesc: boolean;
environmentDesc: string;
}
@inject('store') // 注入
@observer // 观察者
class Login extends React.Component<{ [props: string]: any }, State> {
constructor(props) {
super(props);
this.state = {
loading: false,
captchaLoading: false,
captcha: {},
isShow: true,
answer: '',
redirectUrl: '',
openEnvironmentDesc: true,
environmentDesc: ''
};
}
// 模块渲染后 立即执行的
componentDidMount() {
localStorage.removeItem('Authorization');
this.getCaptcha(); // 获取验证码
this.getRedirectUrl();
this.initEnvironment(); // 初始化环境
}
initEnvironment = () => {
const href = window.location.href;
if (href.indexOf('nrp-dev') > -1) {
this.setState({
environmentDesc: '开发环境'
});
}
if (href.indexOf('nrp-test') > -1) {
this.setState({
environmentDesc: '测试环境'
});
}
if (href.indexOf('nrp-uat') > -1) {
this.setState({
environmentDesc: 'Uat环境'
});
}
if (href.indexOf('localhost') > -1) {
this.setState({
environmentDesc: '本地环境'
});
}
};
getRedirectUrl = () => {
if (window.location.search && window.location.search.indexOf('=') > -1) {
const redirectUrl = window.location.search.split('=')[1];
this.setState({
redirectUrl
});
}
};
//调用接口
getCaptcha = async () => {
this.setState({ captchaLoading: true });
const res: any = await service.getCaptcha();
if (res.success) {
this.setState({ captcha: res.data });
} else {
ElNotification({
type: 'error',
message: res.msg
});
}
this.setState({ captchaLoading: false });
};
// 登录,下边有调用
login = async (params) => {
this.setState({ loading: true });
const res: any = await service.login({
...params,
capuid: this.state.captcha.uuid
});
this.setState({ loading: false });
if (res.success) {
// 浏览器本地缓存token
localStorage.setItem(
'Authorization',
`${res.data.token_type} ${res.data.access_token}`
);
// 全局数据栈缓存用户信息
const userRes = await service.getCurrent();
if (userRes.success) {
const orgRes = await service.getOrgInfo();
if (orgRes.success) {
this.props.store.updatePrincipal({
...userRes.data,
buData: orgRes.data
});
}
}
const redirectUrl = this.state.redirectUrl;
this.setState({
redirectUrl: ''
});
// 跳转至首页
this.props.history.push(redirectUrl ? redirectUrl : '/dashboard', {
replace: true
});
} else {
// 重新获取验证码
this.getCaptcha();
ElNotification({
type: 'error',
message: res.msg
});
}
};
// 页面交互事件处理
handleRefreshCaptcha = () => {
this.getCaptcha();
};
handleLogin = (values) => {
values.password = enCodeStr(values.password);
this.login(values);
};
handleValidateFail = ({ errorFields }) => {
ElNotification({
type: 'error',
message: errorFields[0].errors[0]
});
};
render() {
return (
<div className={'container'}>
<div className={'content'}>
<i className={'logo'} />
<i className={'img'}>
<span style={{ fontSize: '80px', color: 'red' }}>
{this.state.environmentDesc}
</span>
</i>
<Tabs className={'tabs'} tabBarGutter={72}>
<TabPane tab='账号密码登录' key='1'>
<Form
className={'form'}
onFinish={this.handleLogin}
onFinishFailed={this.handleValidateFail}
initialValues={{
username: '',
password: ''
}}
>
<Form.Item
name='username'
required
rules={[{ required: true, message: '请输入账户名' }]}
>
<Input
prefix={<UserBlack className={'icon'} />}
className={'input'}
placeholder='账户名'
autoComplete='off'
/>
</Form.Item>
<Form.Item
name='password'
required
rules={[{ required: true, message: '请输入密码' }]}
>
<Input.Password
prefix={<LockBlack className={'icon'} />}
className={'input'}
placeholder='密码'
autoComplete='off'
/>
</Form.Item>
<Form.Item
name='captcha'
required
rules={[{ required: true, message: '请输入验证码' }]}
>
<Input
className={'captchaInput'}
placeholder='验证码'
autoComplete='off'
addonAfter={
<div
onClick={this.handleRefreshCaptcha}
style={{ width: 80 }}
>
{this.state.captchaLoading ? (
<LoadingBlack />
) : (
<img
src={this.state.captcha && this.state.captcha.img}
alt='captcha'
className={'captcha'}
/>
)}
</div>
}
/>
</Form.Item>
{/* <Form.Item>
<Form.Item name='remember' valuePropName='checked' noStyle>
<Checkbox disabled={true} className={'checkbox'}>
自动登录
</Checkbox>
</Form.Item>
<a
style={{ opacity: 0.2 }}
className={'forget'}
href='javascript:return false;'
>
忘记密码?
</a>
</Form.Item> */}
<Form.Item>
<Button
type='primary'
htmlType='submit'
className={'input'}
loading={this.state.loading}
>
登录
</Button>
{this.state.isShow ? (
<div id='answer'>{this.state.captcha.captext}</div>
) : null}
</Form.Item>
</Form>
</TabPane>
{/* <TabPane tab='手机号登录' key='2' /> */}
</Tabs>
<span className={'footer'}>
Copyright© 2021 云时通产品研发技术部出品{' '}
<a href='https://beian.miit.gov.cn/#/Integrated/recordQuery'>
沪ICP备18031690号-1
</a>
</span>
</div>
</div>
);
}
}
export default Login;
这样就结束了。
====================
项目2 中我们直接上代码
import React from "react";
import ReactDOM from 'react-dom'
import {
HashRouter as Router,
Route,
Link,
Redirect,
withRouter
} from "react-router-dom";
解说
1. 点击公共页面
2. 单击受保护页面
3. 登陆
4. 单击后退按钮,每次注意URL
const AuthExample = () => (
<Router>
<div>
<AuthButton />
<ul>
<li>
<Link to="/public">公共页面</Link>
</li>
<li>
<Link to="/protected">需要登录验证的页面</Link>
</li>
// 或者
{/* <li>
<Link to={{ pathname: '/login', state: { from: { pathname: '/abc' } } }}>登录页面</Link>
</li> */}
</ul>
<Route path="/public" component={Public} />
<Route path="/login" component={Login} />
{/* PrivateRoute 自定义 的路由组件实现路由拦截功能 通过将props,path="/protected" component={Protected}传过去*/}
<PrivateRoute path="/protected" component={Protected} />
</div>
</Router>
);
const fakeAuth = {
// fakeAuth登录的对象集合
isAuthenticated: false, // 登录状态
// 登录方法
authenticate(cb) {
this.isAuthenticated = true;
setTimeout(cb, 100); // fake async
},
// 退出登录方法
signout(cb) {
this.isAuthenticated = false;
setTimeout(cb, 100);
}
};
// withRouter 方法重新包装router
const AuthButton = withRouter(
({ history }) =>
// 登录状态 登录了就显示退出, 没有登录就显示没有登录
fakeAuth.isAuthenticated ? (
<p>
Welcome!{" "}
<button
onClick={() => {
// 函数式路由跳转
fakeAuth.signout(() => history.push("/"));
}}
>
退出登录
</button>
</p>
) : (
<p>您没有登录</p>
)
);
自己对 Route 组件做的一个封装。(高阶组件)
// props.path
// props.component
// rest = { path: ‘/protected’ }
// 自己定义的一个Router组件,来实现路由拦截功能
// component: Component,将props中的component解构赋值出来并且实施重命名,应为下面是要作为组件使用
// …rest 是接受剩下的元素
const PrivateRoute = ({ component: Component, ...rest }) => (
<Route
// 这是在路由中将剩余的props元素展开
{...rest} // 里面有路径 和路由页面的三props属性
render={
//这里的props是路由三prop
props =>
// 登录对象集合中的登录状态
fakeAuth.isAuthenticated ? (
// 为true 就显示该组件
<Component {...props} />
) : (
<Redirect
// 登录状态为false时重定向到登录页面
to={{
pathname: "/login",
state: { from: props.location }
}}
/>
)
}
/>
);
const Public = () => <h3>公共页面</h3>;
const Protected = () => <h3>需要登录的页面</h3>;
//登录页面
class Login extends React.Component {
state = {
// 登录状态
redirectToReferrer: false
};
login = () => {
// 点击登录时触发登录方法
fakeAuth.authenticate(() => {
// 将redirectToReferrer登录状态转变为true
this.setState({ redirectToReferrer: true });
});
};
render() {
// 解构赋值到 from 有传递过来想去的页面就去这个页面 没有的话就去 /
const { from } = this.props.location.state || { from: { pathname: "/" } };
const { redirectToReferrer } = this.state;
// 如果登录状态为true 就重定向到from页面
if (redirectToReferrer) {
return <Redirect to={from} />;
}
return (
<div>
<p>你还没有权限需要先登录 {from.pathname}</p>
<button onClick={this.login}>登录</button>
</div>
);
}
}
// export default AuthExample;
ReactDOM.render(<AuthExample />, document.getElementById('root'))