22 项目实战:详情页面和登录功能开发(三)

登陆页面布局

这节,我们来做登录页面的布局
1.先写登录页面的路由

//===>src/App.js
import React from 'react';
import Header from './common/header'
import store from './store'
import { Provider } from 'react-redux'
import { BrowserRouter, Route } from 'react-router-dom'
import Home from './pages/home';
import Detail from './pages/detail'
import Login from './pages/login'
function App() {
  return (
    <Provider store={store}>
      <BrowserRouter>
        <div>
          <Header />
          <Route path='/' exact component={Home}></Route>
          <Route path='/login' exact component={Login}></Route>
          <Route path='/detail/:id' exact component={Detail}></Route>
        </div>
      </BrowserRouter>
    </Provider >
  );
}

export default App;

2.Login组件布局

//===>src/pages/login/index.js
import React, { PureComponent } from 'react'
import { connect } from 'react-redux'
import { LoginWrapper, LoginBox, Input, Buttom } from './style'
class Login extends PureComponent {
    render() {
        return (
            <LoginWrapper>
                <LoginBox>
                    <Input placeholder='账号' />
                    <Input placeholder='密码' />
                    <Buttom>登录</Buttom>
                </LoginBox>
            </LoginWrapper>
        )
    }
}

const mapState = (state) => ({

})

const mapDispatch = (dispatch) => ({

})

export default connect(mapState, mapDispatch)(Login)

3.编写布局样式

//===>src/pages/login/style.js
import styled from 'styled-components'

export const LoginWrapper = styled.div`
z-index:0;
position:absolute;
left:0;
right:0;
bottom:0;
top:56px;
background:#eee;
`

export const LoginBox = styled.div`
width:400px;
height:180px;
margin:100px auto;
padding-top:20px;
background:#fff;
box-shadow:0 0 8px rgba(0,0,0,.1);
`

export const Input = styled.input`
display:block;
width:200px;
height:30px;
line-height:30px;
padding:0 10px;
margin:10px auto;
color:#777;
`

export const Buttom = styled.div`
width:220px;
height:30px;
line-height:30px;
color:#fff;
background:#3194d0;
border-radius:15px;
margin:10px auto;
text-align:center;
`

我们注意到在LoginWrapper的样式中,我们加入了z-index:0;他的作用是把Login页面置于Header底层,否则不能显示出Header中Search组件的框框了。所以我们还要在Header组件的style文件中,提高Search的z-index

//===>src/common/header/style.js
...
export const HeaderWrapper = styled.div`
z-index:1;
position:relative;
height:56px;
border-bottom:1px solid #f0f0f0;
`
...
效果

登陆功能实现

我们要实现登录登出功能。表现方式是:未登录状态,Header组件右边显示登陆;已登录状态,Header组件右边显示退出。未登录时候,跑/login地址,填表后,点击登录登陆状态改变,并跳转至首页。
1.先写一个登录模拟接口

//===>public/api/login.json
{
    "success": true,
    "data": true
}

2.我们写登录相关常量

//===>src/pages/login/store/constants.js
export const CHANGE_LOGIN = 'login/CHANGE_LOGIN'
export const CHANGE_LOGOUT = 'login/CHANGE_LOGOUT'

3.然后要写actionCreators

//===>src/pages/login/store/actionCreators.js
import axios from 'axios'
import * as constants from './constants'

const changeLogin = () => ({
    type: constants.CHANGE_LOGIN,
    value: true
})

export const logout = () => ({
    type: constants.CHANGE_LOGOUT,
    value: false
})


export const login = (account, password) => {
    return (dispatch) => {
        axios.get('/api/login.json?account=' + account + '&password' + password)
            .then((res) => {
                const result = res.data.data
                if (result) {
                    dispatch(changeLogin())
                } else {
                    alert('登陆失败')
                }
            })
    }
}

4.reducer

//===>src/pages/login/store/reducer.js
import { fromJS } from 'immutable'
import * as constants from './constants'

const defaultState = fromJS({
    login: false
})

export default (state = defaultState, action) => {
    switch (action.type) {
        case constants.CHANGE_LOGIN:
            return state.set('login', action.value)
        case constants.CHANGE_LOGOUT:
            return state.set('login', action.value)
        default:
            return state
    }
}

5.login的reducer写好了,我们还要把他注册到全局reducer中

//===>src/store/reducer.js
import { combineReducers } from 'redux-immutable'
import { reducer as headerReducer } from '../common/header/store'
import { reducer as homeReducer } from '../pages/home/store'
import { reducer as detailReducer } from '../pages/detail/store'
import { reducer as loginReducer } from '../pages/login/store'

const reducer = combineReducers({
    header: headerReducer,
    home: homeReducer,
    detail: detailReducer,
    login: loginReducer
})

export default reducer

6.我们把这些东西暴露出来

//===>src/pages/login/store/index.js
import reducer from './reducer'
import * as actionCreators from './actionCreators'
import * as constants from './constants'

export { reducer, actionCreators, constants }

7.接下来是header组件和login页面逻辑

//===>src/common/header/index.js
import React, { Component, Fragment } from 'react'
import { CSSTransition } from 'react-transition-group'
import {
    HeaderWrapper,
    Logo,
    Nav,
    NavItem,
    SearchWrapper,
    NavSearch,
    SearchInfo,
    SearchInfoTitle,
    SearchInfoSwitch,
    SearchInfoList,
    SearchInfoItem,
    Addition,
    Button
} from './style'
import { IconFontStyle } from '../../statics/iconfont/iconfont';
import { connect } from 'react-redux';
import { actionCreators } from './store';
import { Link } from 'react-router-dom'
import { actionCreators as loginActionCreators } from '../../pages/login/store'

class Header extends Component {

    getListArea() {
        const { focused, list, page, totalPage, mouseIn, handleMouseEnter, handleMouseLeave, handleChangePage } = this.props
        const newList = list.toJS()
        const pageList = []
        if (newList.length) {
            for (let i = ((page - 1) * 10); i < page * 10; i++) {
                pageList.push(
                    <SearchInfoItem key={newList[i]}>{newList[i]}</SearchInfoItem>
                )
            }
        }
        if (focused || mouseIn) {
            return (
                <SearchInfo onMouseEnter={handleMouseEnter}
                    onMouseLeave={handleMouseLeave}>
                    <SearchInfoTitle>
                        热门搜索
                        <SearchInfoSwitch onClick={() => handleChangePage(page, totalPage, this.spinIcon)}>
                            <span ref={(icon) => { this.spinIcon = icon }} className="iconfont spin">&#xe851;</span>换一批
                        </SearchInfoSwitch>
                    </SearchInfoTitle>
                    <SearchInfoList>
                        {pageList}
                    </SearchInfoList>
                </SearchInfo>
            )
        } else {
            return null;
        }
    }

    render() {
        const { focused, handleInputFocus, handleInputBlur, list, login, logout } = this.props
        return (
            <Fragment>
                <IconFontStyle />
                <HeaderWrapper>
                    <Link to='/'>
                        <Logo />
                    </Link>
                    <Nav>
                        <NavItem className='left'>首页</NavItem>
                        <NavItem className='left'>下载App</NavItem>
                        {
                            login
                                ?
                                <NavItem onClick={logout} className='right'>退出</NavItem>
                                :
                                <Link to='/login'><NavItem className='right'>登录</NavItem></Link>
                        }
                        <NavItem className='right'>
                            <span className="iconfont">&#xe636;</span>
                        </NavItem>
                        <SearchWrapper>
                            <CSSTransition
                                in={focused}
                                timeout={200}
                                classNames="slide">
                                <NavSearch
                                    className={focused ? 'focused' : ''}
                                    onFocus={() => handleInputFocus(list)}
                                    onBlur={handleInputBlur}>
                                </NavSearch>
                            </CSSTransition>
                            <span className={focused ? 'focused iconfont zoom' : 'iconfont zoom'}>&#xe662;</span>
                            {this.getListArea()}
                        </SearchWrapper>
                        <Addition>
                            <Button className='writting'>
                                <span className="iconfont">&#xe6e5;</span>写文章
                            </Button>
                            <Button className='reg'>注册</Button>
                        </Addition>
                    </Nav>
                </HeaderWrapper>
            </Fragment>
        )
    }
}

const mapStateToProps = (state) => {
    return {
        // focused: state.get('header').get('focused')
        focused: state.getIn(['header', 'focused']),
        list: state.getIn(['header', 'list']),
        page: state.getIn(['header', 'page']),
        totalPage: state.getIn(['header', 'totalPage']),
        mouseIn: state.getIn(['header', 'mouseIn']),
        login: state.getIn(['login', 'login'])
    }
}

const mapDispatchToProps = (dispatch) => {
    return {
        handleInputFocus(list) {
            if (list.size === 0) {
                dispatch(actionCreators.getList())
            }
            dispatch(actionCreators.searchFocus())
        },
        handleInputBlur() {
            dispatch(actionCreators.searchBlur())
        },
        handleMouseEnter() {
            dispatch(actionCreators.mouseEnter())
        },
        handleMouseLeave() {
            dispatch(actionCreators.mouseLeave())
        },
        handleChangePage(page, totalPage, spin) {
            let originAngle = spin.style.transform.replace(/[^0-9]/ig, '')
            if (originAngle) {
                originAngle = parseInt(originAngle, 10)
            } else {
                originAngle = 0
            }
            spin.style.transform = 'rotate(' + (originAngle + 360) + 'deg)'
            if (page < totalPage) {
                dispatch(actionCreators.changePage(page + 1))
            } else {
                dispatch(actionCreators.changePage(1))
            }
        },
        logout() {
            dispatch(loginActionCreators.logout())
        }
    }
}

export default connect(mapStateToProps, mapDispatchToProps)(Header)
//===>src/pages/login/index.js
import React, { PureComponent } from 'react'
import { connect } from 'react-redux'
import { LoginWrapper, LoginBox, Input, Buttom } from './style'
import { actionCreators } from './store'
import { Redirect } from 'react-router-dom'

class Login extends PureComponent {
    render() {
        const { loginStatus } = this.props;
        if (!loginStatus) {
            return (
                <LoginWrapper>
                    <LoginBox>
                        <Input placeholder='账号' ref={(input) => { this.account = input }} />
                        <Input placeholder='密码' type='password' ref={(input) => { this.password = input }} />
                        <Buttom onClick={() => this.props.login(this.account, this.password)}>登录</Buttom>
                    </LoginBox>
                </LoginWrapper>
            )
        } else {
            return <Redirect to='/' />
        }
    }
}

const mapState = (state) => ({
    loginStatus: state.getIn(['login', 'login'])
})

const mapDispatch = (dispatch) => ({
    login(accountElem, passwordElem) {
        dispatch(actionCreators.login(accountElem.value, passwordElem.value))
    }
})

export default connect(mapState, mapDispatch)(Login)
image.png
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。