react项目开发-权限布局(前三篇续)

基于create-react-app官方脚手架搭建dva模式的项目-权限布局的开发,之前的项目骨架已经可以按照你自己的业务和项目逻辑去组装汽车了。

这里依然以上项目骨架为基础,增加权限布局的开发。

权限设计思路:

1 用户一旦登录成功,后台会返回一个token令牌,此令牌的形式有很多种,传统的就是session-cookie机制了,也可以采用比较流行的一种令牌机制JWT(有兴趣的同学可自行学习),个人推荐已此种方式做令牌。

2 前端会把token存在浏览器端,比如sessionStorge存储机制,之后每次请求均会在请求的中带上这个token传给服务端,服务端校验此令牌,若合法则通过,视为正常用户,若不合法则返回无权限。

3 用户点击退出,或者后台返回不合法,前端则请求退出接口,并清空临时数据,返回登录界面。

4 涉及登录安全,在开发环境也增加https访问模式,修改package.json文件中scripts配置项:

[html]  view plain  copy
  1. "start": "cross-env PORT=9999 HTTPS=true node scripts/start.js",  

下面我们在之前基础上进行开发

1 routes目录下新建login目录,里面新建index.js和index.less文件作为登录页面:

(1)不要忘了接入store数据

(2)登录页面,需要表单Form组件支持

(3)用到了样式,本项目用到classnames (可自行学习),安装:cnpm i classnames --save

(4)要用到css-module,这里有个点:直接开启module会导致和antd冲突,故此开启方式,修改webpack.config.dev.js和webpack.config.prod.js,在本文件中复制一份css-loader配置部分,改为开启less的module,这份配置排在前面

代码如下:

[javascript]  view plain  copy
  1. {  
  2.             test: /\.less$/,  
  3.             use: [  
  4.               require.resolve('style-loader'),  
  5.               {  
  6.                 loader: require.resolve('css-loader'),  
  7.                 options: {  
  8.                   importLoaders: 1,  
  9.                   modules: true,  
  10.                   localIdentName:"[name]__[local]___[hash:base64:5]"  
  11.                 },  
  12.               },  
  13.               {  
  14.                 loader: require.resolve('postcss-loader'),  
  15.                 options: {  
  16.                   // Necessary for external CSS imports to work  
  17.                   // https://github.com/facebookincubator/create-react-app/issues/2677  
  18.                   ident: 'postcss',  
  19.                   plugins: () => [  
  20.                     require('postcss-flexbugs-fixes'),  
  21.                     autoprefixer({  
  22.                       browsers: [  
  23.                         '>1%',  
  24.                         'last 4 versions',  
  25.                         'Firefox ESR',  
  26.                         'not ie < 9'// React doesn't support IE8 anyway  
  27.                       ],  
  28.                       flexbox: 'no-2009',  
  29.                     }),  
  30.                   ],  
  31.                 },  
  32.               },  
  33.               {  
  34.                 loader:require.resolve('less-loader'),  
  35.                 options: { javascriptEnabled: true }  
  36.               }  
  37.             ],  
  38.           },  
  39.           {  
  40.             test: /\.(css|less)$/,  
  41.             use: [  
  42.               require.resolve('style-loader'),  
  43.               {  
  44.                 loader: require.resolve('css-loader'),  
  45.                 options: {  
  46.                   importLoaders: 1,  
  47.                 },  
  48.               },  
  49.               {  
  50.                 loader: require.resolve('postcss-loader'),  
  51.                 options: {  
  52.                   // Necessary for external CSS imports to work  
  53.                   // https://github.com/facebookincubator/create-react-app/issues/2677  
  54.                   ident: 'postcss',  
  55.                   plugins: () => [  
  56.                     require('postcss-flexbugs-fixes'),  
  57.                     autoprefixer({  
  58.                       browsers: [  
  59.                         '>1%',  
  60.                         'last 4 versions',  
  61.                         'Firefox ESR',  
  62.                         'not ie < 9'// React doesn't support IE8 anyway  
  63.                       ],  
  64.                       flexbox: 'no-2009',  
  65.                     }),  
  66.                   ],  
  67.                 },  
  68.               },  
  69.               {  
  70.                 loader:require.resolve('less-loader'),  
  71.                 options: { javascriptEnabled: true }  
  72.               }  
  73.             ],  
  74.           },  


关与开启module和antd冲突解决方法网上找了下,大家可参考:

https://segmentfault.com/a/1190000011225917

https://segmentfault.com/q/1010000011965218

https://www.jianshu.com/p/51ff1c8be301

https://blog.csdn.net/nongweiyilady/article/details/79939761

登录页面代码如下:

login/index.js

[javascript]  view plain  copy
  1. import React, {Component} from 'react';  
  2. import {connect} from 'dva'  
  3. import {injectIntl} from 'react-intl'  
  4. import {Row, Col, Form, Icon, Input, Button} from 'antd'  
  5. import classnames from 'classnames';  
  6. import styles from './index.less';  
  7.   
  8. const FormItem = Form.Item  
  9.   
  10. class Login extends Component{  
  11.   
  12.     loginSubmit=(e)=>{  
  13.         e.preventDefault();  
  14.         const {form} = this.props;  
  15.         form.validateFields((err, values) => {  
  16.             if (!err) {  
  17.               console.log(values);  
  18.             }  
  19.         });  
  20.     }  
  21.   
  22.     render(){  
  23.         const {form} = this.props;  
  24.         const {getFieldDecorator} = form;  
  25.         return(  
  26.             <Row>  
  27.                 <Col className={classnames(styles.loginFormCol)}>  
  28.                     <Form onSubmit={this.loginSubmit} className={classnames(styles.loginForm)}>  
  29.                         <h3>登录</h3>  
  30.                         <FormItem>  
  31.                         {getFieldDecorator('username', {  
  32.                             rules: [{   
  33.                                 required: true,   
  34.                                 message: '请输入用户名'   
  35.                             }],  
  36.                         })(  
  37.                             <Input prefix={<Icon type="user" style={{ color: 'rgba(0,0,0,.25)' }} />} placeholder="用户名" />  
  38.                         )}  
  39.                         </FormItem>  
  40.                         <FormItem>  
  41.                         {getFieldDecorator('password', {  
  42.                             rules: [{   
  43.                                 required: true,   
  44.                                 message: '请输入密码'   
  45.                             }],  
  46.                         })(  
  47.                             <Input prefix={<Icon type="lock" style={{ color: 'rgba(0,0,0,.25)' }} />} type="password" placeholder="密码" />  
  48.                         )}  
  49.                         </FormItem>  
  50.                         <FormItem>  
  51.                             <Button type="primary" htmlType="submit" className={classnames(styles.loginBtn)}>  
  52.                                 登录  
  53.                             </Button>  
  54.                         </FormItem>  
  55.                     </Form>  
  56.                 </Col>  
  57.             </Row>  
  58.         )  
  59.     }  
  60. }  
  61.   
  62. export default connect(({  
  63.   
  64. })=>({  
  65.   
  66. }))(injectIntl(Form.create()(Login)))  

login/index.less

[javascript]  view plain  copy
  1. .loginFormCol{  
  2.     height:100vh;  
  3.     display:flex;  
  4.     justify-content:center;  
  5.     flex-direction:column;  
  6.     .loginForm{  
  7.         width:300px;  
  8.         height:190px;  
  9.         margin:0 auto;  
  10.         .loginBtn{  
  11.             width:100%  
  12.         }  
  13.     }  
  14. }  

最终效果:


2 修改models/app.js文件,增加login.js的model

(1)增加token存储字段,用于将来存储令牌的字段,值取自sessionStorage.getItem('token'),对应增加对其的实时更新effects和reducers:updateToken和updateStore

(2)增加locationPathname字段,用于存储当前url的pathname,对应增加对其的实时更新effects和reducers:updateLocation

代码如下:

[javascript]  view plain  copy
  1.   
import { Map, fromJS} from 'immutable';
import { routerRedux} from 'dva/router';
const initState = Map({
i18n: 'zh_CN',
token: null,
locationPathname: null,
})
export default {
namespace: 'app',
state:initState,
subscriptions: {
setup({ dispatch, history }) {
},
setupHistory ({ dispatch, history }) {
history. listen(( location) => {
dispatch({
type: 'updateLocation',
payload: {
locationPathname: location. pathname
},
});
dispatch({
type: 'updateToken',
payload: {
token: window. sessionStorage. getItem( 'token')
},
})
})
},
},
effects: {
* changeLang ({
payload: { value},
}, { put }) {
yield put({ type: 'updateLang', payload: { value}});
},
* updateLocation ({
payload
}, { put, select}) {
yield put({ type: 'updateStore', payload});
},
* updateToken ({
payload
}, { put, select}) {
yield put({ type: 'updateStore', payload});
},
* logout ({
payload
}, { put, select}) {
window. sessionStorage. removeItem( 'token');
window. location. href= '/login';
},
},
reducers: {
updateLang ( state,{ payload:{ value}}) {
return state. set( 'i18n', value);
},
updateStore ( state, { payload }) {
return payload? state. mergeDeep( fromJS( payload)): initState
},
},
};

(3)models/login.js代码:

[html]  view plain  copy
  1. export default {  
  2.   
  3.     namespace: 'login',  
  4.     
  5.     state: {  
  6.       name:'这是login的model'  
  7.     },  
  8.     
  9.     subscriptions: {  
  10.         
  11.     },  
  12.     
  13.     effects: {  
  14.         
  15.     },  
  16.     
  17.     reducers: {  
  18.         
  19.     },  
  20.     
  21.   };  
  22.     

3 src目录下增加目录layout,用于存放布局组件

(1)layout下创建auth.js,权限组件,用户包裹全局组件:

[javascript]  view plain  copy
  1. import {connect} from 'dva';  
  2. import React from 'react';  
  3.   
  4. const Auth=({ children,dispatch,token,locationPathname })=>{  
  5.   
  6.   if(!token&&locationPathname!='/login'){  
  7.     dispatch({  
  8.       type:'app/logout'  
  9.     })  
  10.   }  
  11.   
  12.   return (  
  13.     <React.Fragment>  
  14.       {children}  
  15.     </React.Fragment>  
  16.   );  
  17. }  
  18.   
  19. export default connect(({  
  20.   app  
  21. })=>({  
  22.   token:app.get('token'),  
  23.   locationPathname:app.get('locationPathname'),  
  24. }))(Auth)  

(2)把locale.js国际化组件,移到layout目录下,目录调整如下:


4 src/route.js修改,包裹auth组件:

[html]  view plain  copy
  1. import React from 'react';  
  2. import { Router, Route, Switch } from 'dva/router';  
  3. import dynamic from 'dva/dynamic'  
  4. import Locale from './layout/locale'  
  5. import Auth from './layout/auth'  
  6. import {config} from './utils'  
  7. const { menuGlobal } = config  
  8.   
  9. function RouterConfig({ history, app }) {  
  10.   
  11.   return (  
  12.     <Auth>  
  13.       <Locale>  
  14.         <Router history={history}>  
  15.           <Switch>  
  16.             {  
  17.               menuGlobal.map(({path,...dynamics},index)=>(  
  18.                 <Route  
  19.                   key={index}   
  20.                   path={path}   
  21.                   exact   
  22.                   component={dynamic({  
  23.                     app,  
  24.                     ...dynamics  
  25.                   })}   
  26.                 />  
  27.               ))  
  28.             }  
  29.           </Switch>  
  30.         </Router>  
  31.       </Locale>  
  32.     </Auth>  
  33.   );  
  34. }  
  35.   
  36. export default RouterConfig;  

下面验证一下效果:

直接访问:https://localhost:9999/aaa , 你会发现已经跳转到了https://localhost:9999/login页面,当我们再sessionStorage中模拟一个token=123456,再次访问/aaa即可进入,当清除掉token则退出到/login,效果如下:


5 routes目录下,新建home,作为登录后的首页,目录如下:


index.js

[html]  view plain  copy
  1. import React, {Component} from 'react';  
  2. import {connect} from 'dva'  
  3. import {Link} from 'dva/router'  
  4. import {injectIntl} from 'react-intl'  
  5. import {Row, Col, Form, Button} from 'antd'  
  6. import classnames from 'classnames';  
  7. import styles from './index.less';  
  8.   
  9. class Home extends Component{  
  10.   
  11.     render(){  
  12.         return(  
  13.             <Row>  
  14.                 <Col className={classnames(styles.home)}>  
  15.                     欢迎您,来到首页  
  16.                 </Col>  
  17.                 <Col>  
  18.                     <Link to={'/aaa'}><Button>去AAA页面</Button></Link>  
  19.                 </Col>  
  20.             </Row>  
  21.         )  
  22.     }  
  23. }  
  24.   
  25. export default connect(({  
  26.   
  27. })=>({  
  28.   
  29. }))(injectIntl(Form.create()(Home)))  

index.less

[html]  view plain  copy
  1. .home{  
  2.     padding:20px;  
  3. }  

6 models目录下新建home.js

[html]  view plain  copy
  1. export default {  
  2.   
  3.     namespace: 'home',  
  4.     
  5.     state: {  
  6.       name:'这是home的model'  
  7.     },  
  8.     
  9.     subscriptions: {  
  10.         
  11.     },  
  12.     
  13.     effects: {  
  14.         
  15.     },  
  16.     
  17.     reducers: {  
  18.         
  19.     },  
  20.     
  21.   };  
  22.     

7 修改utils/config.js 代码:

[html]  view plain  copy
  1. const menuGlobal=[  
  2.     {  
  3.         id:'login',  
  4.         pid:'0',  
  5.         name:'登录',  
  6.         icon:'user',  
  7.         path: '/login',  
  8.         models: () => [import('../models/login')], //models可多个  
  9.         component: () => import('../routes/login'),  
  10.     },   
  11.     {  
  12.         id:'home',  
  13.         pid:'0',  
  14.         name:'首页',  
  15.         icon:'user',  
  16.         path: '/',  
  17.         models: () => [import('../models/home')], //models可多个  
  18.         component: () => import('../routes/home'),  
  19.     },   
  20.     {  
  21.         id:'aaa',  
  22.         pid:'0',  
  23.         name:'aaa页',  
  24.         icon:'user',  
  25.         path: '/aaa',  
  26.         models: () => [import('../models/aaa')], //models可多个  
  27.         component: () => import('../routes/AAA'),  
  28.     },   
  29.     {  
  30.         id:'bbb',  
  31.         pid:'0',  
  32.         name:'bbb页',  
  33.         icon:'user',  
  34.         path: '/aaa/bbb',  
  35.         models: () => [import('../models/bbb')], //models可多个  
  36.         component: () => import('../routes/BBB'),  
  37.     },   
  38.     {  
  39.         id:'ccc',  
  40.         pid:'0',  
  41.         name:'ccc页',  
  42.         icon:'user',  
  43.         path: '/ccc',  
  44.         models: () => [import('../models/ccc')], //models可多个  
  45.         component: () => import('../routes/CCC'),  
  46.     },   
  47.   ];  
  48.     
  49. export default {  
  50.     menuGlobal  
  51. }  

8 我们暂时约定账号为admin,密码为123456

(1) 修改/routes/login/index.js

[html]  view plain  copy
  1. import React, {Component} from 'react';  
  2. import {connect} from 'dva'  
  3. import {injectIntl} from 'react-intl'  
  4. import {Row, Col, Form, Icon, Input, Button} from 'antd'  
  5. import classnames from 'classnames';  
  6. import styles from './index.less';  
  7.   
  8. const FormItem = Form.Item  
  9.   
  10. class Login extends Component{  
  11.   
  12.     loginSubmit=(e)=>{  
  13.         e.preventDefault();  
  14.         const {form,dispatch} = this.props;  
  15.         form.validateFields((err, values) => {  
  16.             if (!err) {  
  17.               dispatch({  
  18.                   type:'login/login',  
  19.                   payload:{  
  20.                       values  
  21.                   }  
  22.               })  
  23.             }  
  24.         });  
  25.     }  
  26.   
  27.     render(){  
  28.         const {form} = this.props;  
  29.         const {getFieldDecorator} = form;  
  30.         return(  
  31.             <Row>  
  32.                 <Col className={classnames(styles.loginFormCol)}>  
  33.                     <Form onSubmit={this.loginSubmit} className={classnames(styles.loginForm)}>  
  34.                         <h3>登录</h3>  
  35.                         <FormItem>  
  36.                         {getFieldDecorator('username', {  
  37.                             rules: [{   
  38.                                 required: true,   
  39.                                 message: '请输入用户名'   
  40.                             }],  
  41.                         })(  
  42.                             <Input autoComplete={'off'} prefix={<Icon type="user" style={{ color: 'rgba(0,0,0,.25)' }} />placeholder="用户名" />  
  43.                         )}  
  44.                         </FormItem>  
  45.                         <FormItem>  
  46.                         {getFieldDecorator('password', {  
  47.                             rules: [{   
  48.                                 required: true,   
  49.                                 message: '请输入密码'   
  50.                             }],  
  51.                         })(  
  52.                             <Input autoComplete={'off'} prefix={<Icon type="lock" style={{ color: 'rgba(0,0,0,.25)' }} />type="password" placeholder="密码" />  
  53.                         )}  
  54.                         </FormItem>  
  55.                         <FormItem>  
  56.                             <Button type="primary" htmlType="submit" className={classnames(styles.loginBtn)}>  
  57.                                 登录  
  58.                             </Button>  
  59.                         </FormItem>  
  60.                     </Form>  
  61.                 </Col>  
  62.             </Row>  
  63.         )  
  64.     }  
  65. }  
  66.   
  67. export default connect(({  
  68.   
  69. })=>({  
  70.   
  71. }))(injectIntl(Form.create()(Login)))  

(2) 修改models/login.js

[html]  view plain  copy
  1. import {message} from 'antd';  
  2.   
  3. export default {  
  4.   
  5.     namespace: 'login',  
  6.     
  7.     state: {  
  8.       name:'这是login的model'  
  9.     },  
  10.     
  11.     subscriptions: {  
  12.         
  13.     },  
  14.     
  15.     effects: {  
  16.       * login ({  
  17.           payload  
  18.       }, {put, select}) {  
  19.           if(payload.values.username=='admin'&&payload.values.password=='123456'){  
  20.             //登录成功  
  21.             yield put({  
  22.               type:'app/loginOk',  
  23.               payload:{  
  24.                 token:'123abc'  
  25.               }  
  26.             });  
  27.           }else{  
  28.             message.warning('用户名或密码不正确')  
  29.           }  
  30.       },  
  31.   
  32.     },  
  33.     
  34.     reducers: {  
  35.         
  36.     },  
  37.     
  38.   };  
  39.     

(3) 修改models/app.js

[html]  view plain  copy
  1. import {Map, fromJS} from 'immutable';  
  2. import {routerRedux} from 'dva/router';  
  3.   
  4. const initState = Map({  
  5.     i18n: 'zh_CN',  
  6.     token:null,  
  7.     locationPathname:null,  
  8. })  
  9.   
  10. export default {  
  11.   
  12.     namespace: 'app',  
  13.     
  14.     state:initState,  
  15.     
  16.     subscriptions: {  
  17.         setup({ dispatch, history }) {  
  18.           
  19.         },  
  20.         setupHistory ({ dispatch, history }) {  
  21.             history.listen((location) => {  
  22.                 dispatch({  
  23.                     type: 'updateLocation',  
  24.                     payload: {  
  25.                       locationPathname: location.pathname  
  26.                     },  
  27.                 });  
  28.                 dispatch({  
  29.                     type: 'updateToken',  
  30.                     payload: {  
  31.                       token: window.sessionStorage.getItem('token')  
  32.                     },  
  33.                 })  
  34.             })  
  35.         },  
  36.     },  
  37.     
  38.     effects: {  
  39.   
  40.         * changeLang ({  
  41.             payload: {value},  
  42.         }, { put }) {  
  43.             yield put({ type: 'updateLang', payload: {value}});  
  44.         },  
  45.   
  46.         * updateLocation ({  
  47.             payload  
  48.         }, {put, select}) {  
  49.             yield put({type: 'updateStore', payload});  
  50.         },  
  51.   
  52.         * updateToken ({  
  53.             payload  
  54.         }, {put, select}) {  
  55.             yield put({type: 'updateStore', payload});  
  56.         },  
  57.   
  58.         * loginOk ({  
  59.             payload  
  60.         }, {put, select}) {  
  61.             window.sessionStorage.setItem('token',payload.token);  
  62.             yield put(routerRedux.push({  
  63.                 pathname: '/'  
  64.             }));  
  65.         },  
  66.   
  67.         * logout ({  
  68.             payload  
  69.         }, {put, select}) {  
  70.             window.sessionStorage.removeItem('token');  
  71.             window.location.href='/login';  
  72.         },  
  73.           
  74.     },  
  75.     
  76.     reducers: {  
  77.         updateLang (state,{payload:{value}}) {  
  78.             return state.set('i18n',value);  
  79.         },  
  80.         updateStore (state, { payload }) {  
  81.             return payload?state.mergeDeep(fromJS(payload)):initState  
  82.         },  
  83.           
  84.     },  
  85.     
  86.   };  
  87.     

(4) 修改layout/auth.js

[html]  view plain  copy
  1. import {connect} from 'dva';  
  2. import React from 'react';  
  3.   
  4. const Auth=({ children,dispatch,token,locationPathname })=>{  
  5.   
  6.   if(!token&&locationPathname!='/login'){  
  7.     dispatch({  
  8.       type:'app/logout'  
  9.     })  
  10.   }else if(token&&locationPathname=='/login'){  
  11.     dispatch({  
  12.       type:'app/loginOk',  
  13.       payload:{  
  14.         token:token  
  15.       }  
  16.     })  
  17.   }  
  18.   
  19.   return (  
  20.     <React.Fragment>  
  21.       {children}  
  22.     </React.Fragment>  
  23.   );  
  24. }  
  25.   
  26. export default connect(({  
  27.   app  
  28. })=>({  
  29.   token:app.get('token'),  
  30.   locationPathname:app.get('locationPathname'),  
  31. }))(Auth)  


至此,我们看下效果:



  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值