實戰react技術棧+express先後端博客項目(8)-- 前端管理界面標籤管理+後端對應接口開發

項目地址: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)

Home.js 路由判斷重定向

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是在太大了因此這裏就放兩張圖片,你們略微感覺下。

  • 初始狀態

  • 添加tag

狀態]

  • 前端標籤變化

  • 刪除tag

態]

  • 回到初始狀態,/Vue刷新會Redirect到404

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 (新建)

---

相關文章
相關標籤/搜索