React技术栈构建后台管理系统模版

之前一直开发的技术栈主要是VueJS相关,平时也有阅读React文档,但是没有把React相关技术栈串联起来,形成一个后台管理系统的模板。在学习的过程之中,基于React开发推荐的create-react-app脚手架搭建管理系统模板。

开发依赖

antd: "^3.19.1",
axios: "^0.19.0",
bizcharts: "^3.5.3",
react: "^16.8.6",
react-dom: "^16.8.6",
react-redux: "^7.0.3",
react-router-dom: "^5.0.0",
react-transition-group: "^4.1.0",
redux: "^4.0.1"   
复制代码

git仓库地址

React-admin-system

运行效果

开发这个管理系统主要目的是理清React-Router v4React-ReduxRedux等常用类库的布置和使用。在掘金浏览许多关于React技术开发的文章。关于管理系统对于用户登录权限认证和系统路由布置使用以及对ajax第三方类库二次封装等文章比较少。所以下面会比较着重描述一下个人开发过程中这部分的实现。

 

login

 

 

 

dataCount

 

 

 

progress

 

 

 

404

 

 

目录分析

开发对于请求使用的是第三方请求库axios,对于axios的封装主要在平时Vuejs开发时候对请求封装实践。开发主要部分是util/fakeAuth.jsrouter/PrivateRoute.js,这部分是对用户登录状态验证。而components/ContentMain是登录后的主要路由设置。

├─public(静态资源目录)
├─screenShot(开发现阶段运行截图)
├─service(service服务Koa)
│  ├─bin
│  ├─config
│  ├─controller
│  ├─model
│  ├─routes
│  └─util
└─src
    ├─api(请求二次封装)
    ├─assets(开发静态资源目录)
    │  └─images
    ├─components(图标类组件)
    │  ├─AreaMap
    │  ├─Chart
    │  ├─ContentMain
    │  ├─Diagram
    │  ├─Line
    │  ├─Map
    │  └─SiderNav(sider侧边导航栏)
    ├─redux(store组件)
    ├─router(路由组件)
    ├─util(权限验证函数)
    └─views(主要开发组件)
        ├─Alert
        ├─Avatar
        ├─Basic
        ├─Cascader
        ├─Checkbox
        ├─DataChart
        ├─Form
        ├─Index
        ├─Login
        ├─Message
        ├─NoMatch
        ├─Progress
        └─Spin
复制代码

login用户登录状态验证

包装PrivateRoute权限路由

React-Router的路由组件Route,包装路由组件使需要登录获取权限的路由,需要自动认证再进行渲染。渲染组件需要通过权限认证函数fakeAuth.authenticate()进行验证,该函数会判别是否存在后端接口返回的token值,分别渲染不同组件。

import React from 'react';
import {Route,Redirect} from 'react-router-dom';
import {fakeAuth} from '../util/fakeAuth';
const PrivateRoute = ({component:Component,...rest})=>{
    return (
        <Route
            {...rest}
            render = {props => (fakeAuth.authenticate())?(<Component {...props}/>):(
                <Redirect to={{
                    pathname:"/login",
                    state:{from:props.location}
                }}/>
            )}
        ></Route>
    )
}
export default PrivateRoute;
复制代码

fakeAuth验证函数

fakeAuth文件包含三个函数authenticatesetTokensignout,以上分别对应验证登录权限、设置登录token、清除登录token。

export const fakeAuth = {
    authenticate() {
        const token = sessionStorage.getItem("loginToken");
        return !!token ? true : false;
    },
    setToken(token) {
        sessionStorage.setItem("loginToken", token);
        return true;
    },
    signout() {
        sessionStorage.removeItem('loginToken');

    }
};
复制代码

login登录验证详细

login.js通过fakeAuth.authenticate()验证,然后分别渲染不同组件。<Redirect>组件在这里主要有两个作用:

  • 用户登录获取权限路由权限后,在浏览器地址栏重新输入/login路由会自动返回权限路由路由首页。
  • 对于未登录用户,自动跳转到登录页。
import React,{Component} from 'react';
import {Redirect} from 'react-router-dom';
import {fakeAuth} from '../../util/fakeAuth';
import LoginForm from './index';
export default class Login extends Component{
    render(){
        return (
            fakeAuth.authenticate()?<Redirect to="/"></Redirect>:<LoginForm />
        )
    }
}
复制代码

登录请求代码

登录认证请求代码在views/login/loginForm中,handleSubmit是登录事件函数,主要对获取用户权限接口获取token,然后进行路由跳转.this.props.history.pushReact-Router v4版本特有的写法,其他版本可以参考官方文档。要获取到this.props.history需要withRouter对组件传入location,match,history等信息。

    handleSubmit = e => {
        e.preventDefault();
        this.props.form.validateFields((err, values) => {
          if (!err) {
            //这里需要对后端接口请求,调试中
            fakeAuth.setToken("zxcvbnmasdfghjkl");
            this.props.history.push('/dataCount');
            message.success('登陆成功',1);
            return;
          }
        });
    }
复制代码

App.js入口文件

login是公开的路由,其他需要权限的路由包含在PrivateRoute组件中,最后一个如果没有组件匹配,最后匹配的是我们自定义的404页面

import React from 'react';
import {BrowserRouter as Router,Route,Switch} from 'react-router-dom';
import {Provider} from 'react-redux';
import {store} from './redux/index.js';
import {CSSTransition} from "react-transition-group";
import Login from './views/Login/login';
import Index from './views/Index';
import PrivateRoute from './router/PrivateRoute';
import NoMatch from './views/NoMatch';
import history from './api/history';
import './App.css';
function App() {
  return (
    <Provider store={store}>
      <Router basename="/echo" history={history}>
          <div className="entryWrap">
              <CSSTransition
                classNames="fade"
                timeout={1000}
              >
                <Switch>
                  <Route path="/login" component={Login} exact/>
                  <PrivateRoute path="/" component={Index}/>
                  <Route component={NoMatch}/>
                </Switch>
              </CSSTransition>
          </div>
      </Router>
    </Provider>
  );
}
export default App;
复制代码

ContentMain权限路由渲染页面

ContentMain主要是配置了权限路由的配置。使用PrivateRoute包装路由,方便性由此可见。配置路由没有比较难逻辑,如下所示:

import React,{Component} from 'react';
import {Switch,withRouter,Route} from 'react-router-dom';
import PrivateRoute from '../../router/PrivateRoute';
import {routes} from '../../router/route';
import NoMatch from '../../views/NoMatch';
import './index.css';
class ContentMain extends Component{
    render(){
        return(
            <div className="routeWrap">
                <Switch>
                    {
                        routes.map(item=>{
                            return (
                                item.path?<PrivateRoute path={item.path} exact component={item.component}/>:<Route component={NoMatch}/>
                            )
                        })
                    }
                    
                </Switch>
            </div>
        )
    }
}
export default withRouter(ContentMain);
复制代码

权限route路由配置

上面权限路由所有配置在router/route中,有点类似于vue-router的配置方式。

import DataChart from '../views/DataChart';
import Basic from '../views/Basic';
import Form from '../views/Form';
import Message from '../views/Message';
import Alert from '../views/Alert';
import Spin from '../views/Spin';
import Progress from '../views/Progress';
import Checkbox from '../views/Checkbox';
import Cascader from '../views/Cascader';
export const routes = [
    {
        path: '/dataCount',
        component: DataChart
    },
    {
        path: '/basic',
        component: Basic
    },
    {
        path: '/form',
        component: Form
    },
    {
        path: "/alert",
        component: Alert
    },
    {
        path: '/message',
        component: Message
    },
    {
        path: '/spin',
        component: Spin
    },
    {
        path: '/progress',
        component: Progress
    },
    {
        path: '/checkbox',
        component: Checkbox
    },
    {
        path: '/cascader',
        component: Cascader
    }]
复制代码

axios二次封装统一请求处理

api/index.js主要包含了对axios第三方请求库的二次封装,包含错误统一处理以及不同请求方法GET、POST、DELETE、PUT等统一风格的api。关于对token请求权限过期,返回请求状态码401,清除token,重定向到/logn处理逻辑也会在这里。在这一部分,Vuejs的处理逻辑也很类似,但是比较熟悉可以直接通过以下方式进行重定向:

import router from "../router/index.js";
router.replace({
    path: "/login",
    query: {
        redirect: router.currentRoute.path
    }
});

复制代码

但是对于React-Router,网上浏览比较多的文章,尝试多比较多遍,都没有找到一个不是强制刷新的路由方案,使用window.location.href可以强制跳转,但是这里使用了createBrowserHistory方法进行重定向。如果有其他的方案可以给我留言。菜鸡的我才能茁壮成长。下面是关于封装的代码部分:

import axios from "axios"; 
import {fakeAuth} from '../util/fakeAuth';
import {message as Message} from 'antd';
import {timeout,baseURL} from "./config.js";
import history from './history';
axios.defaults.timeout = timeout;
axios.defaults.baseURL = baseURL;
axios.interceptors.request.use(
    config => {
        if (fakeAuth.authenticate()) {
            config.headers.Authorization = `Bearer ${sessionStorage.getItem('loginToken')}`;
        }
        return config;
    },
    error => {
        return Promise.reject(error);
    }
);
axios.interceptors.response.use(
    response => {
        return response;
    },
    error => {
        if (error.response) {
            switch (error.response.status) {
                case 401:
                    fakeAuth.signout();
                    history.push('/login');
                    break;
                default:
            }
            const message = error.response.data.message ?error.response.data.message :"服务器异常";
            Message.error(message);
        }
        return Promise.reject(error);
    }
);
const gl_ajax = params => {
    return axios({
            method: params.method.toLowerCase(),
            url: `${axios.defaults.baseURL}${params.url}`,
            data: params.method !== "get" ? params.data : "",
            params: params.method === "get" ? params.data : "",
            responseType: params.file ? "blob" : ""
        })
        .then(res => {
            params.success && params.success(res);
        })
        .catch(err => {
            params.error && params.error(err);
        });
};
export default gl_ajax;
复制代码

history文件部分是参考了网友给的技术方案,利用了createBrowserHistory({forceRefresh:true}),这里show出来给大家参考一下:

// history.js
import { createBrowserHistory } from "history";
export default createBrowserHistory({forceRefresh:true}); 

// config.js
export const timeout = 5000;
export const baseURL =
  process.env.NODE_ENV === "production"
    ? "https://echo/api/v1"
    : "http://localhost:8080/api/v1";
复制代码

登录token过期处理

redux之类的第三方存储,不需要借助router, 只要收到401请求的时候, 修改redux auth.isTokenTimeout为 true, 界面就跳到超时登陆了, 而且url没改动,这样登陆成功后界面还能依据url自动还原。

上面token过期路由跳转方案是在react-china论坛一位网友提出,感觉很不错。解决方案地址

export const AuthorizeApplication = connect(x=> x.auth)(function Authorize({isLogin, isTokenTimeout}){
    if (isTokenTimeout) { return <TokenTimeoutSign /> }
    if (!isLogin) { return <Sign /> }
    return <Application />
})
复制代码

如果觉得喜欢可以给个小星星(star)吗?

React-admin-system

参考文章
[译] 2019 React Redux 完全指南
[译]React中的用户认证(登录态管理)
React Router 4.x开发,这些雷区我们都帮你踩过了


作者:Harhao
链接:https://juejin.im/post/5cf52714f265da1b7b31758e
来源:掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

展开阅读全文

没有更多推荐了,返回首页