github:https://github.com/Ching-Lee/react_news
1.環(huán)境搭建
1.1 使用react腳本架搭建項目(詳情參見第一節(jié))
創(chuàng)建項目:
進入項目目錄執(zhí)行如下命令矫付。
create-react-app react-news
進入項目目錄
cd react-news
npm start
清除App.js中的內(nèi)容
import React, { Component } from 'react';
import './App.css';
class App extends Component {
render() {
return (
<h1>Init</h1>
);
}
}
export default App;
1.2 引入Ant Design的UI框架
https://ant.design/docs/react/use-with-create-react-app-cn
- 進入項目目錄,執(zhí)行:
npm install --save antd
- 修改App.js亥鬓,引入antd的Button組件
import React, { Component } from 'react';
import './App.css';
import Button from 'antd/lib/button';
class App extends Component {
render() {
return (
<div>
<h1>Init</h1>
<Button type="primary">Button</Button>
</div>
);
}
}
export default App;
- 修改App.css,引入antd的css樣式
@import '~antd/dist/antd.css';
.App {
text-align: center;
}
....
-
說明引入成功
效果如圖
2.路由配置
npm install react-router@2.8 --save
在app.js中使用媒體查詢配置路由瞄勾,大于1224加載PCAPP組件,小于1224的設(shè)備加載MobileApp組件
npm install react-responsive --save
class App extends Component {
render() {
return (
<div>
<MediaQuery query='(min-device-width:1224px)'>
<Router history={hashHistory}>
<Route path='/' component={PCApp}>
<IndexRoute component={PCNewsContainer}/>
</Route>
</Router>
</MediaQuery>
<MediaQuery query='(max-device-width:1224px)'>
<Router history={hashHistory}>
<Route path='/' component={MobileApp}/>
</Router>
</MediaQuery>
</div>
);
}
}
3.PC端實現(xiàn)
頭部和尾部是所有頁面共享的。
中間部分根據(jù)路由顯示不同的組件视粮。
import React from 'react';
import PCHeader from '../../component/pc/header/pc_header';
import PCFooter from '../../component/pc/footer/pc_footer';
export default class PCApp extends React.Component{
constructor(props){
super(props);
}
render(){
return(
<div>
<PCHeader/>
{this.props.children}
<PCFooter/>
</div>
);
}
}
3.1 頭部組件<PCHeader/>的實現(xiàn)
- 使用ant design的響應(yīng)式設(shè)計,左邊空2列,中間4列是logo阅嘶,18列是導(dǎo)航欄
export default class PCHeader extends React.Component {
constructor(props) {
super(props);
this.state = {
hasLogined: false,//表示是否登陸
userName: '', //表示用戶名
userId: '', //表示用戶id
current: 'top',//表示當前點擊的導(dǎo)航
modalVisable: false, //表示登錄注冊的模態(tài)框是否顯示
};
}
//組件加載之前,判斷其localstorage是否有值,如果有值载迄,設(shè)置state中的用戶名和用戶id
//設(shè)置登陸狀態(tài)為true
//此時顯示用戶名和退出按鈕讯柔,即Logout組件
componentWillMount() {
//表示存在id
if (localStorage.userId && localStorage.userId != '') {
this.setState({userId: localStorage.userId, userName: localStorage.userName, hasLogined: true});
}
};
render() {
return (
<header>
<Row>
<Col span={2}></Col>
<Col span={4}>
<a className='logo' href='/'>
<img src={logo} alt='logo'/>
<span>新聞頭條</span>
</a>
</Col>
<Col span={18}>
<Nav hasLogined={this.state.hasLogined} logout={this.logout.bind(this)}
userName={this.state.userName} current={this.state.current}
menuItemClick={this.MenuItemClick.bind(this)}/>
</Col>
</Row>
</header>
);
}
}
-
Nav是導(dǎo)航組件
導(dǎo)航組件根據(jù)用戶是否登陸來顯示(hasLogined),如果登錄了(true)护昧,最右側(cè)顯示用戶名和注銷登錄按鈕魂迄,如果沒有登錄(false)顯示登錄和注冊按鈕。
使用了ant design的menu組件
import React from 'react';
import Logout from './LogoutComponent';
import {Menu, Icon} from 'antd';
import {Link} from 'react-router';
export default class Nav extends React.Component{
render(){
//判斷用戶是否登錄惋耙,用戶登錄就顯示個人中心和退出按鈕
//用戶沒有登錄就顯示注冊/登錄按鈕
const userShow = this.props.hasLogined ?
<Menu.Item key="logout">
<Logout logout={this.props.logout} userName={this.props.userName}/>
</Menu.Item> :
<Menu.Item key='register'>
<Icon type='appstore'/>注冊/登錄
</Menu.Item>;
return(
<Menu mode="horizontal" selectedKeys={[this.props.current]}
onClick={this.props.menuItemClick}>
<Menu.Item key="top">
<Link to='/top'>
<Icon type="appstore"/>頭條
</Link>
</Menu.Item>
<Menu.Item key="shehui">
<Link to='/shehui'>
<Icon type="appstore"/>社會
</Link>
</Menu.Item>
<Menu.Item key="guonei">
<Link to='/guonei'>
<Icon type="appstore"/>國內(nèi)
</Link>
</Menu.Item>
<Menu.Item key="guoji">
<Link to='/guoji'>
<Icon type="appstore"/>國際
</Link>
</Menu.Item>
<Menu.Item key="yule">
<Link to='/yule'>
<Icon type="appstore"/>娛樂
</Link>
</Menu.Item>
<Menu.Item key="tiyu">
<Link to='/tiyu'>
<Icon type="appstore"/>體育
</Link>
</Menu.Item>
<Menu.Item key="keji">
<Link to='/keji'>
<Icon type="appstore"/>科技
</Link>
</Menu.Item>
<Menu.Item key="shishang">
<Link to='/shishang'>
<Icon type="appstore"/>時尚
</Link>
</Menu.Item>
{userShow}
</Menu>
);
}
}
-
其中Logout組件
import React from 'react';
import PropTypes from 'prop-types';
import {Button} from 'antd';
//如果已經(jīng)登錄捣炬,則頭部顯示用戶名和退出按鈕
export default class Logout extends React.Component {
constructor(props) {
super(props);
}
render() {
return (
<div>
<a href='#' target='_blank'><Button type='primary'>{this.props.userName}</Button></a>
<Button type='ghost' onClick={this.props.logout}>注銷用戶</Button>
</div>
);
}
}
//設(shè)置必須需要userName屬性
Logout.propTypes = {
userName: PropTypes.string.isRequired
};
-
注冊功能
點擊注冊登錄按鈕彈出慈格,注冊登錄模態(tài)框。
在pc_header.js中完成相關(guān)代碼遥金。
Nav組件中給Menu綁定了onClick事件
export default class PCHeader extends React.Component {
....
MenuItemClick(e) {
//注冊登錄MenuItem點擊后浴捆,設(shè)置current值,顯示注冊登錄的模態(tài)框
if (e.key === 'register') {
//高亮顯示當前點擊的MenuItem
this.setState({current: 'register'});
//顯示模態(tài)框
this.setModalVisible(true);
} else {
this.setState({current: e.key});
}
}
//設(shè)置注冊和登錄模態(tài)框是否顯示稿械,默認不顯示
setModalVisible(value) {
this.setState({modalVisable: value});
}
}
//return中添加模態(tài)框組件
return(){
...
<Col span={18}>
...
<LoginRegisterModal setModalVisible={this.setModalVisible.bind(this)} login={this.login.bind(this)} visible={this.state.modalVisable}/>
</Col>
}
-
LoginRegisterModal組件
Modal組件中嵌套了Tabs組件选泻,登錄tab和注冊tab
import React from 'react';
import {Tabs, Modal} from 'antd';
import WrappedRegisterForm from './RegisterForm'
import WrappedLoginForm from './LoginForm'
export default class LoginRegisterModal extends React.Component {
handleCancel(){
this.props.setModalVisible(false);
}
render(){
return(
<Modal title="用戶中心" visible={this.props.visible}
onCancel={this.handleCancel.bind(this)}
onOk={this.handleCancel.bind(this)}>
<Tabs type="card">
<Tabs.TabPane tab='登錄' key='1'>
<WrappedLoginForm login={this.props.login} setModalVisible={this.props.setModalVisible}/>
</Tabs.TabPane>
<Tabs.TabPane tab='注冊' key='2'>
<WrappedRegisterForm setModalVisible={this.props.setModalVisible}/>
</Tabs.TabPane>
</Tabs>
</Modal>
);
}
}
- 注冊tab中包裹了注冊表單
<WrappedRegisterForm/>
import React from 'react';
import {Icon, message, Form, Input, Button} from 'antd';
//注冊表單組件
class RegisterForm extends React.Component {
constructor(props) {
super(props);
this.state = {confirmDirty: false};
}
//處理注冊提交表單
handleRegisterSubmit(e) {
//頁面開始向API進行提交數(shù)據(jù)
//阻止submit事件的默認行為
e.preventDefault();
this.props.form.validateFields((err, formData) => {
if (!err) {
console.log('Received values of form: ', formData);
let myFetchOptions = {method: 'GET'};
//發(fā)起注冊數(shù)據(jù)請求
fetch("http://newsapi.gugujiankong.com/Handler.ashx?action=register&username=" + formData.userName + "&password=" + formData.password + "&r_userName=" + formData.r_userName + "&r_password=" + formData.r_password + "&r_confirmPassword=" + formData.r_confirmPassword, myFetchOptions)
.then(response => response.json()).then(json => {
if (json) {
message.success("注冊成功");
//設(shè)置模態(tài)框消失
this.props.setModalVisible(false);
}
});
}
})
}
//注冊驗證確認密碼框輸入的密碼兩次是否一樣
checkPassword(rule, value, callback) {
const form = this.props.form;
if (value && value !== form.getFieldValue('r_password')) {
callback('兩次輸入的密碼不一致!');
} else {
callback();
}
}
//注冊檢驗密碼
checkConfirm(rule, value, callback) {
const form = this.props.form;
if (value && this.state.confirmDirty) {
form.validateFields(['r_confirmPassword'], {force: true});
}
callback();
}
render() {
let {getFieldDecorator} = this.props.form;
return (
<Form onSubmit={this.handleRegisterSubmit.bind(this)}>
<Form.Item lable="賬戶">
{getFieldDecorator('r_userName', {
rules: [{required: true, message: '請輸入您的賬戶!'}],
})
(<Input
prefix={<Icon type="user" style={{color: 'rgba(0,0,0,.25)'}}/>}
placeholder='請輸入您的賬戶'/>)}
</Form.Item>
<Form.Item lable="密碼">
{getFieldDecorator('r_password', {
rules: [{required: true, message: '請輸入您的密碼'}, {
validator: this.checkConfirm.bind(this),
}],
})(
<Input prefix={<Icon type="lock"
style={{color: 'rgba(0,0,0,.25)'}}/>}
type='password' placeholder='請輸入您的密碼'/>)}
</Form.Item>
<Form.Item lable="確認密碼">
{getFieldDecorator('r_confirmPassword', {
rules: [{
required: true, message: '請確認您的密碼!',
}, {
validator: this.checkPassword.bind(this),
}],
})(
<Input prefix={<Icon type="lock"
style={{color: 'rgba(0,0,0,.25)'}}/>}
type='password' placeholder='請再次輸入您的密碼'/>
)}
</Form.Item>
<Form.Item>
<Button type='primary' htmlType='submit'>注冊</Button>
</Form.Item>
</Form>
);
}
}
const WrappedRegisterForm = Form.create()(RegisterForm);
export default WrappedRegisterForm;
-
登錄功能
登錄tab中包裹了登錄表單
<WrappedLoginForm/>組件
import React from 'react';
import {Icon,Form, Input, Button,Checkbox} from 'antd';
import './pc_header.css'
//登錄表單組件
class LoginForm extends React.Component {
constructor(props) {
super(props);
this.state = {hasUser: ''};
}
//motal框中的處理登錄提交表單
handleLoginSubmit(e) {
//頁面開始向API進行提交數(shù)據(jù)
//阻止submit事件的默認行為
e.preventDefault();
this.props.form.validateFields((err, formData) => {
if (!err) {
console.log('Received values of form: ', formData);
let myFetchOptions = {method: 'GET'};
fetch("http://newsapi.gugujiankong.com/Handler.ashx?action=login&username=" + formData.userName + "&password=" + formData.password + "&r_userName=" + formData.r_userName + "&r_password=" + formData.r_password + "&r_confirmPassword=" + formData.r_confirmPassword, myFetchOptions)
.then(response => response.json())
.then(json => {
if (json !== null) {
console.log(json);
let userLogin = {userName: json.NickUserName, userId: json.UserId};
this.props.login(userLogin);
//設(shè)置模態(tài)框消失
this.props.setModalVisible(false);
}
else {
//如果json為null,表示用戶名密碼不存在
this.setState({hasUser: '用戶名或密碼錯誤'});
}
});
}
});
}
render() {
let {getFieldDecorator} = this.props.form;
return (
<Form onSubmit={this.handleLoginSubmit.bind(this)}>
<Form.Item>
{getFieldDecorator('userName', {
rules: [{
required: true,
message: 'Please input your username!'
}],
})(
<Input prefix={<Icon type="user"
style={{color: 'rgba(0,0,0,.25)'}}/>}
placeholder="Username"/>
)}
</Form.Item>
<Form.Item>
{getFieldDecorator('password', {
rules: [{
required: true,
message: 'Please input your Password!'
}],
})(
<Input prefix={<Icon type="lock"
style={{color: 'rgba(0,0,0,.25)'}}/>}
type="password" placeholder="Password"/>
)}
</Form.Item>
<Form.Item>
{getFieldDecorator('remember', {
valuePropName: 'checked',
initialValue: true,
})(
<Checkbox>Remember me</Checkbox>
)}
<span>{this.state.hasUser}</span>
<Button type="primary" htmlType="submit"
className="login-form-button">
Log in
</Button>
</Form.Item>
</Form>
);
}
}
const WrappedLoginForm = Form.create()(LoginForm);
export default WrappedLoginForm;
其中涉及到的login(userLogin)方法美莫,在header.js中
//點擊登錄表單中的登錄按鈕,直接設(shè)置為登錄狀態(tài)
login(userLogin) {
this.setState({userName: userLogin.userName, hasLogined: true, userId: userLogin.userId});
localStorage.userName = userLogin.userName;
localStorage.userId = userLogin.userId;
}
-
注銷功能
Logout組件中的注銷登陸按鈕綁定了logout事件
header.js中l(wèi)ogout事件
//點擊MenuItem中退出登錄按鈕
logout() {
localStorage.userName = '';
localStorage.userId = '';
this.setState({hasLogined: false, userName: '', userId: ''});
};
3.2 頭條首頁內(nèi)容區(qū)
左邊空2列页眯,中間21列,右邊1列
中間21列厢呵,左邊8列窝撵,中間10列,右邊6列
pc_news_container.js
3.2.1 左邊部分實現(xiàn)
pc_news_container中左邊架構(gòu)襟铭。
<Row>
<Col span={2}/>
<Col span={21}>
<Row className='top_news'>
<Col span={8}>
<div className='top_left'>
<Carousel autoplay>
<div><img src={img1}/></div>
<div><img src={img2}/></div>
<div><img src={img3}/></div>
<div><img src={img4}/></div>
</Carousel>
<PCNewsImageBlock count={6} type='guoji' width='100%' cartTitle='國際新聞' justifyContent='space-around' imageWidth='112px' />
</div>
</Col>
<Col span={10}></Col>
<Col span={6}></Col>
</Row>
</Col>
<Col span={1}/>
其中Carousel是輪播圖插件碌奉。
-
PCNewsImageBlock實現(xiàn)
向后臺發(fā)起請求,得到j(luò)son數(shù)據(jù)寒砖,賦給this.state.news赐劣。
傳送給<ImageNewsComponent/>組件。
import React from 'react';
import ImageNewsComponent from './image_news_component';
export default class PCNewsImageBlock extends React.Component {
constructor(props) {
super(props);
this.state = {news: ''};
}
componentDidMount() {
let fetchOption = {method: 'Get'};
fetch("http://newsapi.gugujiankong.com/Handler.ashx?action=getnews&type=" + this.props.type + "&count=" + this.props.count, fetchOption).then(response => response.json()).then(json => this.setState({news: json}));
}
render() {
const news = this.state.news;
let newsImage = news.length ?
<ImageNewsComponent news={news} imageWidth={this.props.imageWidth} cartTitle={this.props.cartTitle} justifyContent={this.props.justifyContent}/>
: '正在加載';
return (
<div>{newsImage}</div>
);
}
}
- <ImageNewsComponent/>組件將獲得的數(shù)據(jù)解析顯示哩都。
每一條數(shù)據(jù)包括image魁兼、h3、p構(gòu)成
import React from 'react';
import {Card} from 'antd';
import {Link} from 'react-router';
import './image_news_component.css';
export default class ImageNewsComponent extends React.Component {
render(){
const news=this.props.news;
const newsList=news.map((newsItem, index) => (
<div key={index} className='image_news_item' style={{width:this.props.imageWidth}}>
<Link to={`details/${newsItem.uniquekey}`} target='_blank'>
<img alt="newsItem.title" src={newsItem.thumbnail_pic_s} width={this.props.imageWidth}/>
<h3>{newsItem.title}</h3>
<p>{newsItem.author_name}</p>
</Link>
</div>
));
return(
<Card className='image_card' title={this.props.cartTitle} bordered={true} style={{width: this.props.width,marginTop:'10px'}}>
<div className='image_news_container' style={{width: this.props.width,justifyContent:this.props.justifyContent}}>
{newsList}
</div>
</Card>
);
}
}
image_news_container采用flexbox布局漠嵌,對齊方式由父組件傳遞而來咐汞,這里調(diào)用時傳遞的是center。
樣式:
.image_news_container{
display:flex;
flex-wrap:wrap;
}
/*圖片框每個item的div*/
.image_news_item{
margin: 0.5rem;
}
/*設(shè)置圖片塊每一項的標題*/
.image_news_item h3{
white-space: nowrap;
overflow:hidden;
text-overflow:ellipsis;
}
/*左邊imageblock的內(nèi)邊距*/
.image_card .ant-card-body{
padding-left: 0px;
padding-right: 0px;
padding-bottom: 14px;
}
3.2.2 中間部分的實現(xiàn)
pc_news_container.js
<Col span={10}>
<div className='top_center'>
<Tabs defaultActiveKey="1">
<Tabs.TabPane tab='頭條新聞' key='1'>
<PCNewsBlock count={30} type='top' width='100%' bordered='false'/>
</Tabs.TabPane>
</Tabs>
</div>
</Col>
-
PC_news_block實現(xiàn)
import React from 'react';
import PCNewsComponent from './pc_news_Component';
export default class PCNewsBlock extends React.Component {
constructor(props) {
super(props);
this.state = {news: ''};
}
//頁面渲染后觸發(fā)
componentDidMount() {
let fetchOption = {method: 'GET'};
fetch("http://newsapi.gugujiankong.com/Handler.ashx?action=getnews&type=" + this.props.type + "&count=" + this.props.count, fetchOption).then(response => response.json()).then(json => this.setState({news: json}));
}
render() {
const news = this.state.news;
//看news的長度是否為0儒鹿,字符串長度為0則是false表示未加載到數(shù)據(jù)化撕,為其他值則true加載到數(shù)據(jù)
const newsCard = news.length ?
<PCNewsComponent news={news}/>
: '正在加載';
return (
<div>
{newsCard}
</div>
);
}
}
- PCNewsComponent實現(xiàn)
解析成li
import {Link} from 'react-router';
import {Card} from 'antd';
import React from 'react';
import './pc_news_component.css'
export default class NewsComponent extends React.Component {
constructor(props){
super(props);
}
render(){
let news=this.props.news;
let newsList=news.map((newsItem, index) => (
<li key={index}>
<Link to={`details/${newsItem.uniquekey}`} target='_blank'>
{newsItem.title}
</Link>
</li>
));
return (
<Card className='news_card'>
<ul>
{newsList}
</ul>
</Card>
);
}
}
3.2.3 右邊部分實現(xiàn)
pc_news_container
<Col span={6}>
<div className='top_right'>
<PCNewsImageSingle width='100%' ImageWidth='100px' type='shehui' count={6} title='社會新聞'/>
</div>
</Col>
- ImageSingle組件
import React from 'react';
import ImageSingleComponent from './imageSingle_component'
export default class PCNewsImageSingle extends React.Component{
constructor(props) {
super(props);
this.state = {news: ''};
}
//頁面渲染之前
componentDidMount() {
let fetchOption = {method: 'GET'};
fetch("http://newsapi.gugujiankong.com/Handler.ashx?action=getnews&type=" + this.props.type + "&count=" + this.props.count, fetchOption).then(response => response.json()).then(json => this.setState({news: json}));
}
render(){
const news=this.state.news;
const newsList=news.length?
<ImageSingleComponent news={news} ImageWidth={this.props.ImageWidth} width={this.props.width} title={this.props.title}/>
:'正在加載';
return(
<div >
{newsList}
</div>
);
}
}
- imageSingleComponent組件
由左邊的圖片和右邊p、span挺身、span構(gòu)成侯谁。
左邊寬度固定,右邊自適應(yīng)布局章钾。
使用flexbox布局墙贱。
import React from 'react';
import {Link} from 'react-router';
import {Card} from 'antd';
import './imageSingle_component.css';
export default class ImageSingleNewComponent extends React.Component{
render(){
const news=this.props.news;
const newsList=news.map((newsItem,index)=>(
<Link to={`details/${newsItem.uniquekey}`} target='_blank' key={index}>
<section className='imageSingle_sec' style={{width:this.props.width}}>
<div className='imageSingle_left' style={{width:this.props.ImageWidth}}>
<img style={{width:this.props.ImageWidth}} src={newsItem.thumbnail_pic_s} alt={newsItem.title}/>
</div>
<div className='imageSingle_right'>
<p>{newsItem.title}</p>
<span className='realType' >{newsItem.realtype}</span>
<span>{newsItem.author_name}</span>
</div>
</section>
</Link>
));
return(
<Card title={this.props.title} className='imageSingleCard'>
{newsList}
</Card>
);
}
}
css
.imageSingle_sec {
border-bottom: thin #E8E8E8 solid;
display: flex;
align-items: center;
box-sizing: content-box;
height: 95px;
padding: 5px 0;
}
.imageSingle_sec .imageSingle_right {
margin-left:1em;
flex:1;
}
.imageSingle_sec .imageSingle_right p{
margin-bottom:0;
}
.imageSingle_sec .imageSingle_right .realType{
color:red;
font-weight:bolder;
margin-right:1em;
}
.imageSingleCard{
height: 736px;
}
3.2.4 最下面部分
<Row>
<PCNewsImageBlock count={16} type='guonei' width='100%' imageWidth='112px' cartTitle='國內(nèi)新聞' justifyContent='space-start'/>
<PCNewsImageBlock count={16} type='yule' width='100%' imageWidth='112px' cartTitle='娛樂新聞' justifyContent='space-start'/>
</Row>
3.3.詳情頁
import React from 'react';
import {Row, Col} from 'antd';
import PCNewsImageBlock from '../../component/pc/topcontent/pc_news_image/pc_news_imageblock';
import Comment from '../../component/common/common_comment';
export default class PCNewsDetail extends React.Component {
constructor() {
super();
this.state = {
newsItem: ''
};
}
componentDidMount() {
let fetchOption = {
method: 'GET'
};
fetch("http://newsapi.gugujiankong.com/Handler.ashx?action=getnewsitem&uniquekey=" + this.props.params.uniquekey, fetchOption)
.then(response => response.json())
.then(json => {
this.setState({newsItem: json});
document.title = this.state.newsItem.title + "-新聞頭條";
});
}
createMarkup() {
return {__html: this.state.newsItem.pagecontent};
}
render() {
return (
<div>
<Row>
<Col span={2}/>
<Col span={14}>
<div style={{marginTop: '50px'}} dangerouslySetInnerHTML={this.createMarkup()}/>
<Comment uniquekey={this.props.params.uniquekey}/>
</Col>
<Col span={1}/>
<Col span={6}>
<PCNewsImageBlock imageWidth='150px' width='100%' count={40} type='top' cartTitle='推薦'/>
</Col>
<Col span={1}/>
</Row>
</div>
);
}
}
3.4.頁腳
import React from 'react';
import {Row,Col} from 'antd';
export default class PCFooter extends React.Component{
constructor(props){
super(props);
}
render(){
return (
<footer>
<Row>
<Col span={2}/>
<Col span={20} style={{ textAlign:'center'}}>
© 2018 新聞頭條。All Rights Reserved.
</Col>
<Col span={2}/>
</Row>
</footer>
);
}
}
4.手機端實現(xiàn)
mobile_app.js
import React from 'react';
import MobileHeader from '../../component/mobile/header/mobile_header';
import MobileFooter from '../../component/mobile/footer/mobile_footer';
import MobileContent from '../../component/mobile/content/mobile_content';
export default class MobileApp extends React.Component {
render() {
return (
<div>
<MobileHeader/>
<MobileContent/>
<MobileFooter/>
</div>
);
}
}
4.1 頭部實現(xiàn)
思路和pc端類似贱傀,點擊icon顯示登陸框惨撇,如果已經(jīng)登錄,點擊就注銷府寒。
export default class MobileHeader extends React.Component {
constructor(props) {
super(props);
this.state = {
current: 'top',
hasLogined: false,
modalVisable: false,
userName: '',
};
}
setModalVisible(value) {
this.setState({modalVisable: value});
}
handleClick() {
this.setModalVisible(true);
}
//組件加載之前魁衙,判斷其localstorage是否有值
componentWillMount(){
//表示存在id
if (localStorage.userId&&localStorage.userId!='') {
this.setState({userId:localStorage.userId,userName:localStorage.userName,hasLogined:true});
}
};
//點擊登錄按鈕
login(userLogin){
this.setState({userName:userLogin.userName,hasLogined:true,userId:userLogin.userId});
localStorage.userName=userLogin.userName;
localStorage.userId=userLogin.userId;
}
logout() {
localStorage.userName = '';
localStorage.userId = '';
this.setState({hasLogined: false, userName: '', userId: ''});
};
render() {
const userShow = this.state.hasLogined ?
<Icon type='inbox' onClick={this.logout.bind(this)}/> : <Icon type='setting' onClick={this.handleClick.bind(this)}/>;
return (
<div id="mobile">
<header>
<Link to='/'><img src={Logo} alt="mobile_logo"/></Link>
<span>新聞頭條</span>
{userShow}
</header>
<LoginRegisterModal setModalVisible={this.setModalVisible.bind(this)} login={this.login.bind(this)}
visible={this.state.modalVisable}/>
</div>
);
}
}
4.2 內(nèi)容區(qū)
- mobile_content.js
使用tab實現(xiàn)报腔,每個tab下由輪播圖和新聞塊構(gòu)成。
import React from 'react';
import img1 from '../../../static/images/carousel_1.jpg';
import img2 from '../../../static/images/carousel_2.jpg';
import img3 from '../../../static/images/carousel_3.jpg';
import img4 from '../../../static/images/carousel_4.jpg';
import {Tabs,Carousel} from 'antd';
import MobileNews from '../../../component/mobile/content/mobile_news';
export default class MobileContent extends React.Component {
render() {
return(
<Tabs>
<Tabs.TabPane tab='頭條' key='1'>
<div>
<Carousel autoplay>
<div><img src={img1}/></div>
<div><img src={img2}/></div>
<div><img src={img3}/></div>
<div><img src={img4}/></div>
</Carousel>
</div>
<MobileNews count={50} type='top' ImageWidth='112px' width='100%'/>
</Tabs.TabPane>
<Tabs.TabPane tab='國內(nèi)' key='3'>
<div>
<Carousel autoplay>
<div><img src={img1}/></div>
<div><img src={img2}/></div>
<div><img src={img3}/></div>
<div><img src={img4}/></div>
</Carousel>
</div>
<MobileNews type='guonei' ImageWidth='112px' width='100%'/>
</Tabs.TabPane>
<Tabs.TabPane tab='國際' key='4'>
<div>
<Carousel autoplay>
<div><img src={img1}/></div>
<div><img src={img2}/></div>
<div><img src={img3}/></div>
<div><img src={img4}/></div>
</Carousel>
</div>
<MobileNews type='guoji' ImageWidth='112px' width='100%'/>
</Tabs.TabPane>
<Tabs.TabPane tab='娛樂' key='5'>
<div>
<Carousel autoplay>
<div><img src={img1}/></div>
<div><img src={img2}/></div>
<div><img src={img3}/></div>
<div><img src={img4}/></div>
</Carousel>
</div>
<MobileNews type='yule' ImageWidth='112px' width='100%'/>
</Tabs.TabPane>
<Tabs.TabPane tab='社會' key='6'>
<div>
<Carousel autoplay>
<div><img src={img1}/></div>
<div><img src={img2}/></div>
<div><img src={img3}/></div>
<div><img src={img4}/></div>
</Carousel>
</div>
<MobileNews type='shehui' ImageWidth='112px' width='100%'/>
</Tabs.TabPane>
<Tabs.TabPane tab='體育' key='7'>
<div>
<Carousel autoplay>
<div><img src={img1}/></div>
<div><img src={img2}/></div>
<div><img src={img3}/></div>
<div><img src={img4}/></div>
</Carousel>
</div>
<MobileNews type='tiyu' ImageWidth='112px' width='100%'/>
</Tabs.TabPane>
<Tabs.TabPane tab='科技' key='8'>
<div>
<Carousel autoplay>
<div><img src={img1}/></div>
<div><img src={img2}/></div>
<div><img src={img3}/></div>
<div><img src={img4}/></div>
</Carousel>
</div>
<MobileNews type='keji' ImageWidth='112px' width='100%'/>
</Tabs.TabPane>
</Tabs>
);
}
}
- MobileNews 新聞模塊實現(xiàn)
import React from 'react';
import MobileNewsComponent from './mobile_news_component'
import LoadMore from './LoadMore'
export default class MobileNews extends React.Component {
constructor(props) {
super(props);
this.state = {
news: [],
};
}
componentDidMount() {
let fetchOption = {method: 'GET'};
fetch("http://newsapi.gugujiankong.com/Handler.ashx?action=getnews&type=" + this.props.type + "&count=" + this.state.count, fetchOption)
.then(response => response.json())
.then(json => this.setState({news: json}));
}
render() {
const news = this.state.news;
const newsList = news.length ?
<MobileNewsComponent news={news} ImageWidth={this.props.ImageWidth}/>
: '正在加載';
return (
<div className='mobile_news'>
{newsList}
</div>
);
}
}
- MobileNewsComponent組件
左邊固定寬度剖淀,右邊自適應(yīng)纯蛾。
import React from 'react';
import {Link} from 'react-router';
import './mobile_news_component.css'
export default class MobileNewsComponent extends React.Component{
render(){
const newlist=this.props.news.map((newsItem, index) => (
<Link to={`details/${newsItem.uniquekey}`} target='_blank' key={index}>
<section className='mob_news_sec'>
<div style={{width:this.props.ImageWidth}}>
<img src={newsItem.thumbnail_pic_s} alt={newsItem.title} style={{width:this.props.ImageWidth}}/>
</div>
<div className='mob_news_right'>
<h3>{newsItem.title}</h3>
<span className='mob_news_realtype'>{newsItem.realtype}</span>
<span>{newsItem.author_name}</span>
</div>
</section>
</Link>
));
return(
<div>
{newlist}
</div>
);
}
}
css樣式
.mob_news_sec{
border:thin #E8E8E8 solid;
padding:0.5rem 0;
display: flex;
align-items: center;
height: 90px;
box-sizing: content-box;
}
.mob_news_sec .mob_news_right{
flex: 1;
margin-left:0.5rem ;
}
.mob_news_sec .mob_news_right .mob_news_realtype{
color:red;
font-weight:bolder;
margin-right: 1em;
}
- 加載更多功能
當點擊加載更多或者已經(jīng)滑動到底部,就會觸發(fā)loadMoreFn纵隔,去獲取后臺數(shù)據(jù)翻诉。
export default class MobileNews extends React.Component {
constructor(props) {
super(props);
this.state = {
news: [],
count: 10,
isLoading:false,
hasMore:true,
};
}
//加載更多方法
loadMoreFn(){
this.setState({isLoading:true});
let count=this.state.count+10;
if (count>0&&count<300){
this.setState({count:count});
let fetchOption = {method: 'GET'};
fetch("http://newsapi.gugujiankong.com/Handler.ashx?action=getnews&type=" + this.props.type + "&count=" + this.state.count, fetchOption)
.then(response => response.json())
.then(json => this.setState({news: json}));
this.setState({isLoadingMore: false});
}else {
this.setState({isLoading:false, hasMore:false})
}
}
...
return (
<div className='mobile_news'>
{newsList}
{
this.state.hasMore?
<LoadMore isLoading={this.state.isLoading} loadMoreFn={this.loadMoreFn.bind(this)}/>
:<div style={{textAlign:'center',backgroundColor:'#F8F8F8'}}>木有更多咯</div>
}
</div>
);
LoadMore組件
import React from 'react';
export default class LoadMore extends React.Component{
constructor(props){
super(props);
}
handleClick(){
this.props.loadMoreFn();
}
componentDidMount(){
const loadMoreFn=this.props.loadMoreFn;
const wrapper=this.refs.wrapper;
let timeoutId;
function callback(){
//得到加載更多div距離頂部的距離
let top=wrapper.getBoundingClientRect().top;
let windowHeight=window.screen.height;
//如果top距離比屏幕距離小,說明加載更多被暴露
if(top&&top<windowHeight)
loadMoreFn();
}
//添加滾動事件監(jiān)聽
window.addEventListener('scroll',function () {
if(this.props.isLoadingMore)
return;
if(timeoutId)
clearTimeout(timeoutId);
//因為一滾動就會觸發(fā)事件捌刮,我們希望50ms才觸發(fā)一次
timeoutId=setTimeout(callback,50);
}.bind(this),false);
}
render(){
return(
<div ref='wrapper' style={{textAlign:'center',backgroundColor:'#F8F8F8'}}>
{
this.props.isLoadingMore?
<span>加載中...</span>
:<span onClick={this.handleClick.bind(this)}>加載更多</span>
}
</div>
);
}
}