Browse Source

antd版本升级调整/登录模块优化/无token时不发送接口请求

zhuth 6 years ago
parent
commit
fb1c504ed4

+ 6 - 2
package.json

@@ -10,7 +10,7 @@
   },
   "dependencies": {
     "ant-design-pro": "^2.0.0-beta.2",
-    "antd": "^3.0.0",
+    "antd": "^3.12.4",
     "app": "^0.1.0",
     "braft-editor": "^1.9.8",
     "canvas2image": "^1.0.5",
@@ -18,10 +18,12 @@
     "dva-loading": "^2.0.3",
     "echarts": "^4.1.0",
     "echarts-for-react": "^2.0.14",
+    "fetch-abort": "^1.0.2",
     "html2canvas": "^1.0.0-alpha.12",
     "jspdf": "^1.4.1",
     "moment": "^2.22.2",
     "prop-types": "^15.6.2",
+    "qrcode.react": "^0.9.3",
     "react": "^16.2.0",
     "react-dom": "^16.2.0",
     "react-grid-layout": "^0.16.6",
@@ -47,6 +49,8 @@
     "eslint-plugin-standard": "^3.1.0",
     "husky": "^0.12.0",
     "redbox-react": "^1.4.3",
-    "roadhog": "^2.5.0-beta.1"
+    "roadhog": "^2.5.0-beta.1",
+    "webpack": "^4.29.6",
+    "webpack-cli": "^3.3.0"
   }
 }

+ 32 - 81
src/components/common/login/login.jsx

@@ -3,37 +3,27 @@ import Login from 'ant-design-pro/lib/Login'
 import { Alert, Checkbox, Icon } from 'antd'
 import { Redirect } from 'dva/router'
 import { connect } from 'dva'
-import * as service from '../../../services/index'
-import URLS from '../../../constants/url'
 import './login.less'
 
 const { UserName, Password, Submit } = Login;
 
-function authenticate(token, expireTime, user, autoLogin, cb ) {
-    window.localStorage.setItem("loginTime", new Date().getTime());
-    window.localStorage.setItem("token", token);
-    window.localStorage.setItem("expireTime", expireTime);
-
-    window.localStorage.setItem("usercode", user.code);
-    autoLogin ? window.localStorage.setItem("autoLogin", 'true') : window.localStorage.setItem("autoLogin", 'false');
-    // autoLogin ? window.localStorage.setItem("account", user.account) : window.localStorage.removeItem('account');
-    window.localStorage.setItem("account", user.account);
-    autoLogin ? window.localStorage.setItem("password", user.password) : window.localStorage.removeItem('password');
-    window.localStorage.setItem("username", user.name);
-    window.localStorage.setItem("userrole", user.role);
-    window.localStorage.setItem("department", user.department);
-    window.localStorage.setItem("job", user.job);
+class LoginComponent extends React.Component {
 
-    setTimeout(cb, 100); // fake async
-}
+    constructor(props) {
+        super(props);
+        const { main } = props;
+        const { currentUser, autoLogin } = main;
+        const { account, password } = currentUser;
 
-class LoginComponent extends React.Component {
-    state = {
-        notice: '',
-        autoLogin: window.localStorage.getItem('autoLogin') ? window.localStorage.getItem('autoLogin') === 'true' : true,
-        redirectToReferrer: false,
-        fetching: false
-    };
+        this.state = {
+            notice: '',
+            currentUserName: account,
+            currentPassword: password,
+            autoLogin: autoLogin,
+            redirectToReferrer: false,
+            fetching: false
+        }
+    }
 
     onSubmit = (err, values) => {
         this.setState({
@@ -54,66 +44,27 @@ class LoginComponent extends React.Component {
         const { dispatch } = this.props;
         const { autoLogin } = this.state;
 
-        let body = {
-            userName: username,
-            passWord: password
-        };
         this.setState({
             fetching: true
-        }, () => {
-            try {
-                service.fetch({
-                    url: URLS.LOGIN,
-                    body: body
-                }).then(r => {
-                    console.log('登录', body, r);
-                    if(!r.err && r.data.code > 0) {
-                        return r.data.data;
-                    }else {
-                        this.setState({
-                            notice: r.err+'' || r.data.msg+'',
-                        });
-                        throw (r.err || r.data.msg);
-                    }
-                }).then(resData => {
-                    const token = resData.token;
-                    const expireTime = resData.times;
-                    const user = resData.user;
-                    const currentUser = {
-                        code: user.id+'',
-                        account: user.userName,
-                        password: user.passWord,
-                        name: user.name,
-                        role: user.role || 'default',
-                        department: user.department,
-                        job: user.post,
-                    };
-                    dispatch({ type: 'main/setCurrentUser', user: currentUser });
-                    authenticate(token, expireTime, currentUser, autoLogin, () => {
-                        this.setState({
-                            redirectToReferrer: true,
-                            fetching: false
-                        });
-                    });
-                }).catch(ex => {
-                    console.error('登录失败', ex);
-                    this.setState({
-                        notice: (ex.message+'' || ex+'') || '登录失败',
-                        fetching: false
-                    });
-                });
-            }catch(e) {
-                console.error(e);
-            }
-        });
+        })
+        dispatch({ type: 'main/login', username, password, autoLogin })
+        .then((d) => {
+            this.setState({
+                notice: '',
+                redirectToReferrer: d,
+                fetching: false
+            })
+        }).catch((r) => {
+            this.setState({
+                notice: r.err+'' || r.data.msg+'',
+                fetching: false
+            });
+        })
     };
 
     render() {
         const { from } = this.props.location.state || { from: { pathname: "/" } };
-        const { notice, autoLogin, fetching, redirectToReferrer } = this.state;
-
-        const defaultAccount = window.localStorage.getItem('account');
-        const defaultPassword = window.localStorage.getItem('password');
+        const { currentUserName, currentPassword, notice, autoLogin, fetching, redirectToReferrer } = this.state;
 
         if (redirectToReferrer) {
             return <Redirect to={from} />;
@@ -140,7 +91,7 @@ class LoginComponent extends React.Component {
                                 disabled={fetching}
                                 placeholder='输入用户名'
                                 // defaultValue={autoLogin ? defaultAccount : ''}
-                                defaultValue={defaultAccount}
+                                defaultValue={currentUserName}
                                 onChange={() => {
                                     this.setState({
                                         notice: ''
@@ -159,7 +110,7 @@ class LoginComponent extends React.Component {
                                 name="password"
                                 disabled={fetching}
                                 placeholder='输入密码'
-                                defaultValue={autoLogin ? defaultPassword : ''}
+                                defaultValue={autoLogin ? currentPassword : ''}
                                 onChange={() => {
                                     this.setState({
                                         notice: ''

+ 106 - 0
src/components/common/login/relogin.jsx

@@ -0,0 +1,106 @@
+import React from 'react'
+import { Modal, Icon, Button, Input, Checkbox  } from 'antd'
+import { connect } from 'dva'
+import './relogin.less'
+
+class Relogin extends React.Component {
+
+    constructor(props) {
+        super(props);
+        const { main } = props;
+        const { currentUser, autoLogin } = main;
+        const { account, password } = currentUser;
+
+        this.state = {
+            currentUserName: account,
+            currentPassword: password,
+            autoLogin: autoLogin,
+            fetching: false
+        }
+    }
+
+    onUsernameChange = (e) => {
+        this.setState({ currentUserName: e.target.value });
+    }
+    
+    onPasswordChange = (e) => {
+        this.setState({ currentPassword: e.target.value });
+    }
+
+    onRemanberChange = (e) => {
+        this.setState({ autoLogin: e.target.checked });
+    }
+
+    onLogin = () => {
+        const { currentUserName, currentPassword, autoLogin } = this.state;
+        const { dispatch } = this.props;
+        this.setState({
+            fetching: true
+        })
+        dispatch({ type: 'main/login', username: currentUserName, password: currentPassword, autoLogin })
+        .then((d) => {
+            this.setState({
+                fetching: false
+            })
+        }).catch((r) => {
+            console.error(r)
+        })
+    }
+
+    render() {
+        const { main, visibleBox } = this.props;
+        const { autoLogin, fetching } = this.state;
+        const { currentUser } = main;
+        const { account, password: defaultPassword } = currentUser;
+
+        return <Modal
+            title="重新登录"
+            className='relogin-box'
+            visible={visibleBox}
+            footer={null}
+            closable={false}
+            centered={true}
+            width={380}
+        >
+            <div className="relogin-item">距离上次登录已经超过30分钟,请重新登录</div>
+            <Input
+                placeholder="用户名"
+                prefix={<Icon type="user" style={{ color: 'rgba(0,0,0,.25)' }} />}
+                defaultValue={account}
+                disabled={true}
+                className="relogin-item"
+                onChange={this.onUsernameChange}
+            ></Input>
+            <Input.Password
+                placeholder="请输入登录密码"
+                prefix={<Icon type="lock" style={{ color: 'rgba(0,0,0,.25)' }} />}
+                className="relogin-item"
+                defaultValue={autoLogin ? defaultPassword : ''}
+                rules={[{
+                    required: true,
+                    whitespace: true,
+                    message: "密码不能为空!",
+                }]}
+                onChange={this.onPasswordChange}
+                onPressEnter={(e) => {
+                    this.onLogin()
+                }}
+            ></Input.Password>
+            <div className="relogin-item remanber">
+                <Checkbox defaultChecked={autoLogin} onChange={this.onRemanberChange}>记住密码</Checkbox>
+            </div>
+            <div className="relogin-item buttons">
+                <Button disabled={fetching} type="primary" onClick={this.onLogin}>
+                    {fetching && <Icon type="loading" theme="outlined" />}
+                    重新登录
+                </Button>
+            </div>
+        </Modal>
+    }
+}
+
+function mapStateToProps({ present: { main } }) {
+    return { main };
+}
+
+export default connect(mapStateToProps)(Relogin)

+ 18 - 0
src/components/common/login/relogin.less

@@ -0,0 +1,18 @@
+.relogin-box {
+    .ant-modal-content {
+        .ant-modal-header {
+            padding: 8px 24px;
+        }
+        .ant-modal-body {
+            padding: 16px 24px;
+
+            .relogin-item {
+                margin-bottom: 8px;
+            }
+
+            .buttons {
+                text-align: center;
+            }
+        }
+    }
+}

+ 0 - 54
src/components/common/rootLayout.jsx

@@ -1,54 +0,0 @@
-import React from 'react'
-import { Modal, Icon, Button } from 'antd'
-import { Redirect } from 'dva/router'
-import './rootLayout.less'
-
-class RootLayout extends React.Component {
-    constructor(props) {
-        super(props);
-        this.state = ({
-            goLogin: false
-        });
-    }
-
-    setGoLogin = () => {
-        this.setState({
-            goLogin: true
-        });
-    }
-
-    render() {
-        const { location, isAuthenticated, children } = this.props;
-        const { goLogin } = this.state;
-        const visibleLoginConfimBox = location !=='/login' && !isAuthenticated;
-        const token = window.localStorage.getItem('token');
-
-        return (<div className='root-layout'>
-            { children }
-            {visibleLoginConfimBox && <Modal
-                className='confirm-box'
-                visible={visibleLoginConfimBox}
-                footer={null}
-                closable={false}
-                centered={true}
-            >
-                <div className='confirm-body'>
-                    <div className='confirm-icon'><Icon type="info-circle" /></div>
-                    <div className='confirm-label'>{token ? '登录已过期' : '未登录'}</div>
-                    <div className='confirm-text'>{token ? '距离上次登录已经超过30分钟,请重新登录' : ''}</div>
-                    <div className='confirm-button'>
-                        <Button type="primary" onClick={this.setGoLogin}>{token ? '重新登录' : '登录'}</Button>
-                    </div>
-                </div>
-            </Modal>}
-            {goLogin && <Redirect
-                to={{
-                    pathname: "/login",
-                    state: { from: location }
-                }}
-            />}
-        </div>)
-    }
-}
-
-export default RootLayout;

+ 0 - 35
src/components/common/rootLayout.less

@@ -1,35 +0,0 @@
-.root-layout {
-    width: 100%;
-    height: 100%;
-}
-.confirm-box {
-    .ant-modal-body {
-        padding: 0;
-        .confirm-body {
-            display: flex;
-            flex-direction: column;
-            text-align: center;
-            .confirm-icon {
-                .anticon {
-                    height: 50px;
-                    line-height: 50px;
-                    font-size: 32px;
-                    color: #FF4D4F;
-                    svg {
-                        margin-top: 10px;
-                    }
-                }
-            }
-            .confirm-label {
-                height: 50px;
-                line-height: 50px;
-                font-size: 24px;
-                font-weight: bold;
-            }
-            .confirm-button {
-                height: 48px;
-                line-height: 48px;
-            }
-        }
-    }
-}

+ 1 - 1
src/components/dataSource/groupSelector.jsx

@@ -40,7 +40,7 @@ class GroupSelector extends React.Component {
                 content={
                     [{code: 'all', label: '全部分组'}, { code: '-1', label: '未分组' }].concat(pgroups).map(p => {
                         let cgroups = grouplist.filter(g => g.pcode === p.code && p.code !== '-1');
-                        return <Row type='flex' justify='left' key={`gr-${p.code}`}>
+                        return <Row type='flex' justify='start' key={`gr-${p.code}`}>
                             <Col key={`rc-${p.code}`} onClick={() => {
                                 this.hide();
                                 dispatch({ type: modelName + '/setCurrentGroup', group1: p });

+ 124 - 19
src/models/main.js

@@ -3,20 +3,31 @@
  */
 import { routerRedux } from 'dva/router'
 import { message } from 'antd'
+import * as service from '../services/index'
+import URLS from '../constants/url'
+import moment from 'moment'
 
-
-const code = window.localStorage.getItem('usercode');
+const code = window.sessionStorage.getItem('usercode');
 const account = window.localStorage.getItem('account');
 const password = window.localStorage.getItem('password');
-const name = window.localStorage.getItem('username');
-const role = window.localStorage.getItem('userrole');
-const department = window.localStorage.getItem('department');
-const job = window.localStorage.getItem('job');
+const name = window.sessionStorage.getItem('username');
+const role = window.sessionStorage.getItem('userrole');
+const department = window.sessionStorage.getItem('department');
+const job = window.sessionStorage.getItem('job');
+const expireTime = window.sessionStorage.getItem('expireTime');
+const autoLogin = window.localStorage.getItem('autoLogin') ? window.localStorage.getItem('autoLogin') === 'true' : true;
+
+const t = moment(+expireTime);
+const isLogin = expireTime && t.isValid();
+const authenticated = t.isValid() && t.diff(moment()) > 0;
 
 export default {
     namespace: 'main',
     state: {
-        authenticated: false,
+        authenticated: authenticated,
+        autoLogin: autoLogin,
+        isLogin: isLogin,
+        expireTime,
         currentUser: {
             code,
             account,
@@ -36,10 +47,13 @@ export default {
             const { user } = action;
             return { ...state, currentUser: user };
         },
-        setAuthenticated(state, action) {
-            const { authenticated } = action;
-            return { ...state, authenticated };
-        }
+        setFields(state, action) {
+            const { fields } = action;
+            let obj = {};
+            fields.map(f => (obj[f.name] = f.value));
+            let newState = Object.assign({}, state, obj);
+            return Object.assign({}, newState );
+        },
     },
     effects: {
         * redirect (action, { put }) {
@@ -60,16 +74,81 @@ export default {
                 window.location.reload();
             }
         },
+        *login(action, {put, call}) {
+            const { username, password, autoLogin } = action;
+
+            function authenticate(token, expireTime, user, autoLogin) {
+                window.sessionStorage.setItem("loginTime", new Date().getTime());
+                window.sessionStorage.setItem("token", token);
+                window.sessionStorage.setItem("expireTime", expireTime);
+            
+                window.sessionStorage.setItem("usercode", user.code);
+                autoLogin ? window.localStorage.setItem("autoLogin", 'true') : window.localStorage.setItem("autoLogin", 'false');
+                window.localStorage.setItem("account", user.account)
+                window.sessionStorage.setItem("account", user.account);
+                autoLogin ? window.localStorage.setItem("password", user.password) : window.localStorage.removeItem('password');
+                window.sessionStorage.setItem("username", user.name);
+                window.sessionStorage.setItem("userrole", user.role);
+                window.sessionStorage.setItem("department", user.department);
+                window.sessionStorage.setItem("job", user.job);
+            }
+
+            const body = {
+                userName: username,
+                passWord: password
+            };
+            try {
+                const res = yield call(service.fetch, {
+                    url: URLS.LOGIN,
+                    body
+                });
+                if(!res.err && res.data.code > 0) {
+                    const resData = res.data.data;
+
+                    const token = resData.token;
+                    const expireTime = resData.times;
+                    const user = resData.user;
+                    const currentUser = {
+                        code: user.id+'',
+                        account: user.userName,
+                        password: user.passWord,
+                        name: user.name,
+                        role: user.role || 'default',
+                        department: user.department,
+                        job: user.post,
+                    };
+                    yield put({ type: 'setFields', fields: [
+                        { name: 'isLogin', value: true },
+                        { name: 'autoLogin', value: autoLogin },
+                        { name: 'authenticated', value: true },
+                        { name: 'currentUser', value: currentUser }
+                    ]});
+                    authenticate(token, expireTime, currentUser, autoLogin);
+                    return true;
+                }else {
+                    message.error('登录失败: ' + (res.err || res.data.msg));
+                    return false;
+                }
+            }catch(e) {
+                yield put({ type: 'setFields', fields: [
+                    { name: 'isLogin', value: false },
+                    { name: 'authenticated', value: false }
+                ]});
+                message.error('登录失败: ' + e);
+                console.error(body, e);
+                return false;
+            }
+        },
         *logout( action, { put, call, select }) {
             try {
-                window.localStorage.removeItem("username");
-                window.localStorage.removeItem("userrole");
-                window.localStorage.removeItem("usercode");
-                window.localStorage.removeItem("department");
-                window.localStorage.removeItem("job");
-                window.localStorage.removeItem("loginTime");
-                window.localStorage.removeItem("token");
-                window.localStorage.removeItem("expireTime");
+                window.sessionStorage.removeItem("username");
+                window.sessionStorage.removeItem("userrole");
+                window.sessionStorage.removeItem("usercode");
+                window.sessionStorage.removeItem("department");
+                window.sessionStorage.removeItem("job");
+                window.sessionStorage.removeItem("loginTime");
+                window.sessionStorage.removeItem("token");
+                window.sessionStorage.removeItem("expireTime");
 
                 yield put({ type: 'dataConnect/reset' });
                 yield put({ type: 'dataSource/reset' });
@@ -92,6 +171,32 @@ export default {
                 duration: 2,
                 maxCount: 3,
             });
+
+            let checkExpireTime = () => {
+                let expireTime = window.sessionStorage.getItem('expireTime');
+                let t = moment(+expireTime).diff(moment())
+                return t >= 0 ? t : 0;
+            }
+        
+            let onExpired = () => {
+                dispatch({ type: 'setFields', fields: [{ name: 'authenticated', value: false}] });
+            }
+
+            let hiddenProperty = 'hidden' in document ? 'hidden' :    
+                'webkitHidden' in document ? 'webkitHidden' :    
+                'mozHidden' in document ? 'mozHidden' :    
+                null;
+            let visibilityChangeEvent = hiddenProperty.replace(/hidden/i, 'visibilitychange');
+            let timeoutKey = window.setTimeout(onExpired, checkExpireTime());
+            let onVisibilityChange = () => {
+                if (document[hiddenProperty]) {
+                    window.clearTimeout(timeoutKey);
+                }else{
+                    timeoutKey = window.setTimeout(onExpired, checkExpireTime());
+                }
+            }
+            document.addEventListener(visibilityChangeEvent, onVisibilityChange);
+
             return history.listen(({ pathname, query }) => {
                 let page = pathname.match(/\/(\w*)/)[1];
                 dispatch({ type: 'setCurrentPage', page });

+ 75 - 0
src/routes/authLayout.jsx

@@ -0,0 +1,75 @@
+import React from 'react'
+import { Redirect } from 'dva/router'
+import Loading from '../components/common/loading/loading'
+import Relogin from '../components/common/login/relogin'
+import { connect } from 'dva'
+import './authLayout.less'
+
+class AuthLayout extends React.Component {
+
+    constructor(props) {
+        super(props);
+        this.state = ({
+            checking: true
+        });
+    }
+
+    componentDidMount = () => {
+        this.t = window.setTimeout(() => {
+            this.setState({
+                checking: false
+            })
+        }, 1000);
+    }
+
+    componentWillUnmount = () => {
+        window.clearTimeout(this.t)
+    }
+
+    render() {
+        const { location, children, main } = this.props;
+        const { isLogin, authenticated,  } = main;
+        const { checking } = this.state
+
+        return (
+            isLogin ? (
+                <div style={{ width: '100%', height: '100%' }}>
+                    { children }
+                    { checking ? (
+                        <div style={{ position: 'absolute', top: 0, height: '100%', width: '100%', zIndex: '4', opacity: 0.5, background: 'rgba(51,51,51,.1)' }}>
+                            <Loading></Loading>
+                        </div>
+                    ) : (
+                        !authenticated && (
+                            <Relogin
+                                visibleBox={true}
+                            ></Relogin>
+                        )
+                    ) }
+                </div>
+            ) : (
+                <div style={{ width: '100%', height: '100%' }}>
+                    { children }
+                    { checking ? (
+                        <div style={{ position: 'absolute', top: 0, height: '100%', width: '100%', zIndex: '4', opacity: 0.5, background: 'rgba(51,51,51,.1)' }}>
+                            <Loading></Loading>
+                        </div>
+                    ) : (
+                        <Redirect
+                            to={{
+                                pathname: '/login',
+                                state: { from: location }
+                            }}
+                        ></Redirect>
+                    ) }
+                </div>
+            )
+        )
+    }
+}
+
+function mapStateToProps({ present: { main } }) {
+    return { main };
+}
+
+export default connect(mapStateToProps)(AuthLayout)

+ 0 - 0
src/routes/authLayout.less


+ 1 - 7
src/routes/mainLayout.js → src/routes/mainLayout.jsx

@@ -1,7 +1,6 @@
 import React from 'react'
 import { Layout } from 'antd'
 import { Route, Switch } from 'dva/router'
-import { Redirect } from 'dva/router'
 import Navigator from '../components/common/navigator'
 import HomePage from '../components/homePage/homePage'
 import Loading from '../components/common/loading/loading'
@@ -30,12 +29,7 @@ const MainLayout = ({ isAuthenticated }) => {
                     <Route sensitive path='/chart' component={Chart} />
                     <Route sensitive path='/admin' component={Admin} />
                     <Route sensitive path='/userinfo' component={UserInfo} />
-                    <Route path='/' component={isAuthenticated ? HomePage : () => <Redirect
-                        to={{
-                            pathname: "/login",
-                            state: { from: '/' }
-                        }}
-                    />}/>
+                    <Route path='/' component={HomePage}/>
                 </Switch>
             </Content>
         </Layout>

+ 4 - 8
src/routes/privateRoute.jsx

@@ -1,17 +1,13 @@
 import { Route } from 'dva/router'
-import RootLayout from '../components/common/rootLayout'
-import moment from 'moment'
+import AuthLayout from './authLayout'
 
 export default ({ component: Component, ...rest }) => (
     <Route
         {...rest}
         render={props =>{
-
-            const t = moment(+window.localStorage.getItem('expireTime'));
-            const isAuthenticated = t.isValid() && t.diff(moment()) > 0;
-            return <RootLayout location={props.location} isAuthenticated={isAuthenticated}>
-                <Component isAuthenticated={isAuthenticated} {...props} />
-            </RootLayout>
+            return <AuthLayout location={props.location}>
+                <Component {...props} />
+            </AuthLayout>
         }}
     />
 );

+ 15 - 9
src/services/index.js

@@ -1,14 +1,20 @@
 import request from '../utils/request'
+import URLS from '../constants/url'
 
 export function fetch(option) {
   const { url, body, timeout } = option;
-  const token = window.localStorage.getItem("token");
-  return request(url, {
-    method: 'POST',
-    headers: {
-      token: token,
-      'Content-Type': 'application/json'
-    },
-    body: JSON.stringify(body),
-  }, timeout);
+  const token = window.sessionStorage.getItem("token");
+  // 除登录请求外,Token不存在时不发送请求
+  if(token || ([URLS.LOGIN].indexOf(url) !== -1)) {
+    return request(url, {
+      method: 'POST',
+      headers: {
+        token: token,
+        'Content-Type': 'application/json'
+      },
+      body: JSON.stringify(body),
+    }, timeout);
+  }else {
+    return new Promise(() => {});
+  }
 }

+ 2 - 2
src/utils/request.js

@@ -5,11 +5,11 @@ function parseJSON(response) {
 }
 
 function checkStatus(response) {
-  if (response.status >= 200 && response.status < 300) {
+  if (response && (response.status >= 200 && response.status < 300)) {
     return response;
   }
 
-  const error = new Error(response.statusText);
+  const error = new Error(response ? response.statusText : 'Error Fetch');
   error.response = response;
   throw error;
 }