项目地址:https://github.com/Nealyang/R...前端
本想等项目作完再连载一波系列博客,随着开发的进行,也是的确遇到了很多坑,请教了很多人。遂想,何不一边记录踩坑,一边分享收获呢。分享固然是好的,
若是能作到集思广益,那岂不是更美。咱们的口号是:坚定不会烂尾react
本博客为连载代码博客同步更新博客,随着项目日后开发可能会遇到前面写的不合适的地方会再回头修改。若有不妥~欢迎兄弟们不啬赐教。谢谢!git
因为该博客内容是开发和发文同步连载,因此在随着开发的进行,会修改以前的开发代码。github
对于权限认证以前咱们只作了一部分权限管理,在前端页面发生跳转的时候,咱们经过检测state的userInfo来肯定当前登陆用户是否有权访问。express
可是,这里存在一个隐患,就是我登陆到一个管理界面以管理员身份,可是知道我身份过时以前都没进行操做和跳转,一直在后端管理界面。而后当我身份过时之后,我进行了admin的一些管理操做(增删改查),且不涉及到页面的跳转。前端无法经过url来判断。redux
因此,这里咱们修改了先后端内容,拦截全部的/admin的api操做,来判断身份是否过时。后端
admin.jsapi
//admin请求后台验证 router.use( (req,res,next) =>{ if(req.session.userInfo){ next() }else{ res.send(responseClient(res,200,1,'身份信息已过时,请从新登陆')); } });
在前端saga里咱们须要判断接口返回的信息。微信
export function* delTagFlow() { while (true){ let req = yield take(ManagerTagsTypes.DELETE_TAG); let res = yield call(delTag,req.name); if (res.code === 0) { yield put({type: IndexActionTypes.SET_MESSAGE, msgContent: res.message, msgType: 1}); yield put({type:ManagerTagsTypes.GET_ALL_TAGS}); } else if (res.message === '身份信息已过时,请从新登陆') { yield put({type: IndexActionTypes.SET_MESSAGE, msgContent: res.message, msgType: 0}); setTimeout(function () { location.replace('/'); }, 1000); } else { yield put({type: IndexActionTypes.SET_MESSAGE, msgContent: res.message, msgType: 0}); } } }
目前我没有想到能够一劳永逸不要每一个saga都作处理的方法,若是兄弟们有好的想法,望不啬赐教,提issue,咱们一块儿讨论网络
以前咱们在前端页面中,front组件直接写的一个函数,后来发现不是很合理,由于front我须要将他变为容器组件。因此这里咱们须要把它抽出来一个class做为container
const {get_all_tags} = actions; class Front extends Component{ constructor(props){ super(props); } render(){ const {url} = this.props.match; return( <div> <div className={`${animationStyle.animated} ${animationStyle.fadeInDown}`}> <Banner/> <Menus categories={this.props.categories} history={this.props.history}/> </div> <Switch> <Route exact path={url} component={Home}/> <Route path={`/detail/:id`} component={Detail}/> <Route path={`/:tag`} component={Home}/> <Route component={NotFound}/> </Switch> </div> ) } componentDidMount() { this.props.get_all_tags(); } } Front.defaultProps = { categories:[] }; Front.propTypes = { categories:PropTypes.array.isRequired }; function mapStateToProps(state) { return{ categories:state.admin.tags } } function mapDispatchToProps(dispatch) { return{ get_all_tags:bindActionCreators(get_all_tags,dispatch) } } export default connect( mapStateToProps, mapDispatchToProps )(Front)
return ( tags.length>1&&this.props.match.params.tag && (tags.indexOf(this.props.match.params.tag) === -1 || this.props.location.pathname.lastIndexOf('\/') > 0) ? <Redirect to='/404'/> : <div className={style.container}> <div className={style.contentContainer}> <div className={`${style.newsContainer} ${anStyle.animated} ${anStyle.fadeInUp}`}> <ArticleList/> <div className={style.paginationContainer}> <Pagination defaultCurrent={6} total={500}/> </div> </div> <div className={`${style.loginContainer} ${anStyle.animated} ${anStyle.fadeInRight}`}> {this.props.userInfo.userId?<Logined history={this.props.history} userInfo={this.props.userInfo}/>:<Login login={login} register={register}/>} </div> </div> </div> )
这里咱们须要判断tags的长度,由于如今tags是异步获取的。因此存在时差。好比命名能够访问/Html标签,可是因为是异步获取的tags,当在当前页面刷新的时候,tags并无加载彻底,因此会直接重定向到404页面。
由于gif是在太大了因此这里就放两张图片,你们略微感觉下。
状态]
态]
g)
//删除标签 router.get('/delTag', function (req, res) { let {name} = req.query; Tags.remove({name}) .then(result => { if(result.result.n === 1){ responseClient(res,200,0,'删除成功!') }else{ responseClient(res,200,1,'标签不存在'); } }).catch(err => { responseClient(res); }); }); //添加标签 router.post('/addTag', function (req, res) { let {name} = req.body; Tags.findOne({ name }).then(result => { if (!result) { let tag = new Tags({ name }); tag.save() .then(data => { responseClient(res, 200, 0, '添加成功', data); }).catch(err => { throw err }) } else { responseClient(res, 200, 1, '该标签已存在'); } }).catch(err => { responseClient(res); }); }); module.exports = router;
为了代码清晰,方便管理,这里直接就分路由到/tag下。操做很常规,就是删除和添加标签。
对于获取所有标签,我放到admin外面,由于毕竟前端页面也须要这个接口。若是都放到/api/admin/getAllTags的话,在/admin请求的时候会进行身份验证。因此将获取所有标签接口放到tags下是不合理的。
这里咱们选择放在main.js中
//获取所有标签 router.get('/getAllTags', function (req, res) { Tags.find(null,'name').then(data => { responseClient(res, 200, 0, '请求成功', data); }).catch(err => { responseClient(res); }) });
对于前端组织结构部分的修改上面已经说完了。这里说下saga中的处理
adminTag界面编码:
class AdminManagerTags extends Component{ constructor(props){ super(props); this.state={ tags: ['首页', 'HTML', 'CSS','JAVASCRIPT'], inputVisible: false, inputValue: '', } } handleClose = (removedTag) => { //删除标签 this.props.deleteTag(removedTag) }; showInput = () => { this.setState({ inputVisible: true }, () => this.input.focus()); }; handleInputChange = (e) => { this.setState({ inputValue: e.target.value }); }; handleInputConfirm = () => { // 添加标签 this.props.addTag(this.state.inputValue); this.setState({ inputVisible: false, inputValue: '', }); }; saveInputRef = input => this.input = input; render(){ const { inputVisible, inputValue } = this.state; const {tags} = this.props; return( <div> <h2 className={style.titleStyle}>标签管理</h2> {tags.map((tag, index) => { const isLongTag = tag.length > 20; const tagElem = ( <Tag className={style.tagStyle} key={index} closable={index !== 0} afterClose={() => this.handleClose(tag)}> {isLongTag ? `${tag.slice(0, 20)}...` : tag} </Tag> ); return isLongTag ? <Tooltip key={tag} title={tag}>{tagElem}</Tooltip> : tagElem; })} {inputVisible && ( <Input className={style.tagStyle} ref={this.saveInputRef} type="text" size="small" style={{ width: 108 }} value={inputValue} onChange={this.handleInputChange} onBlur={this.handleInputConfirm} onPressEnter={this.handleInputConfirm} /> )} {!inputVisible && <Button className={style.tagStyle} size="small" type="dashed" onClick={this.showInput}>+ New Tag</Button>} </div> ) } componentDidMount() { this.props.getAllTags(); } } function mapStateToProps(state) { return{ tags:state.admin.tags } } function mapDispatchToProps(dispatch) { return{ getAllTags : bindActionCreators(get_all_tags,dispatch), deleteTag : bindActionCreators(delete_tag,dispatch), addTag : bindActionCreators(add_tag,dispatch), } } export default connect( mapStateToProps, mapDispatchToProps )(AdminManagerTags)
saga的处理:
export function* delTag(name) { yield put({type: IndexActionTypes.FETCH_START}); try { return yield call(get, `/admin/tags/delTag?name=${name}`); } catch (err) { yield put({type: IndexActionTypes.SET_MESSAGE, msgContent: '网络请求错误', msgType: 0}); } finally { yield put({type: IndexActionTypes.FETCH_END}) } } ... ... export function* delTagFlow() { while (true){ let req = yield take(ManagerTagsTypes.DELETE_TAG); let res = yield call(delTag,req.name); if (res.code === 0) { yield put({type: IndexActionTypes.SET_MESSAGE, msgContent: res.message, msgType: 1}); yield put({type:ManagerTagsTypes.GET_ALL_TAGS}); } else if (res.message === '身份信息已过时,请从新登陆') { yield put({type: IndexActionTypes.SET_MESSAGE, msgContent: res.message, msgType: 0}); setTimeout(function () { location.replace('/'); }, 1000); } else { yield put({type: IndexActionTypes.SET_MESSAGE, msgContent: res.message, msgType: 0}); } } }
操做和以前的都没有两样,须要注意的就是这里返回信息咱们多判断了一层用户信息是否过时以及在saga中的处理。
至此,标签管理也基本完事了。对于前端页面路由的Link仍是history.push这里就不作解释了。你们能够多看看代码。
下一篇咱们将进行文章的操做的。发文,增删改查等功能。
## 交流
假若有哪里说的不是很明白,或者有什么须要与我交流,欢迎各位提issue。或者加群联系我~
扫码关注个人我的微信公众号,直接回复,必有回应。分享更多原创文章。点击交流学习加我微信、qq群。一块儿学习,一块儿进步
---
欢迎兄弟们加入:
Node.js技术交流群:209530601
React技术栈:398240621
前端技术杂谈:604953717 (新建)
---