登陆页面布局
这节,我们来做登录页面的布局
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"></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"></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'}></span>
{this.getListArea()}
</SearchWrapper>
<Addition>
<Button className='writting'>
<span className="iconfont"></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