實戰react技術棧+express前後端部落格專案(8)-- 前端管理介面標籤管理+後端對應介面開發

Neal_yang發表於2017-10-16

專案地址:github.com/Nealyang/Re…

本想等專案做完再連載一波系列部落格,隨著開發的進行,也是的確遇到了不少坑,請教了不少人。遂想,何不一邊記錄踩坑,一邊分享收穫呢。分享當然是好的,
如果能做到集思廣益,那豈不是更美。我們的口號是:堅決不會爛尾

本部落格為連載程式碼部落格同步更新部落格,隨著專案往後開發可能會遇到前面寫的不合適的地方會再回頭修改。如有不妥~歡迎兄弟們不嗇賜教。謝謝!

前面內容修改部分

由於該部落格內容是開發和發文同步連載,所以在隨著開發的進行,會修改之前的開發程式碼。

許可權認證

對於許可權認證之前我們只做了一部分許可權管理,在前端頁面發生跳轉的時候,我們通過檢測state的userInfo來確定當前登入使用者是否有權訪問。

但是,這裡存在一個隱患,就是我登入到一個管理介面以管理員身份,但是知道我身份過期之前都沒進行操作和跳轉,一直在後端管理介面。然後當我身份過期以後,我進行了admin的一些管理操作(增刪改查),且不涉及到頁面的跳轉。前端沒法通過url來判斷。

所以,這裡我們修改了前後端內容,攔截所有的/admin的api操作,來判斷身份是否過期。

admin.js

//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這裡就不做解釋了。大家可以多看看程式碼。

下一篇我們將進行文章的操作的。發文,增刪改查等功能。

專案實現步驟系列部落格


歡迎兄弟們加入:

Node.js技術交流群:209530601

React技術棧:398240621

前端技術雜談:604953717 (新建)


相關文章