11.React实战开发--适配PC及移动端的新闻头条网站
github:https://github.com/Ching-Lee/react_news
1.环境搭建
1.1 使用react脚本架搭建项目(详情参见第一节)
创建项目:
进入项目目录执行如下命令。
create-react-app react-news
进入项目目录
cd react-news
npm start
3000端口
清除App.js中的内容
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
- 进入项目目录,执行:
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的设备加载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端实现
头部和尾部是所有页面共享的。
中间部分根据路由显示不同的组件。
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/>的实现
- 使用ant design的响应式设计,左边空2列,中间4列是logo,18列是导航栏
export default class PCHeader extends React.Component {
constructor(props) {
super(props);
this.state = {
hasLogined: false,//表示是否登陆
userName: '', //表示用户名
userId: '', //表示用户id
current: 'top',//表示当前点击的导航
modalVisable: false, //表示登录注册的模态框是否显示
};
}
//组件加载之前,判断其localstorage是否有值,如果有值,设置state中的用户名和用户id
//设置登陆状态为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是导航组件
导航组件根据用户是否登陆来显示(hasLogined),如果登录了(true),最右侧显示用户名和注销登录按钮,如果没有登录(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"/>国内
</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';
//如果已经登录,则头部显示用户名和退出按钮
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>
);
}
}
//设置必须需要userName属性
Logout.propTypes = {
userName: PropTypes.string.isRequired
};
-
注册功能
点击注册登录按钮弹出,注册登录模态框。
在pc_header.js中完成相关代码。
Nav组件中给Menu绑定了onClick事件
export default class PCHeader extends React.Component {
....
MenuItemClick(e) {
//注册登录MenuItem点击后,设置current值,显示注册登录的模态框
if (e.key === 'register') {
//高亮显示当前点击的MenuItem
this.setState({current: 'register'});
//显示模态框
this.setModalVisible(true);
} else {
this.setState({current: e.key});
}
}
//设置注册和登录模态框是否显示,默认不显示
setModalVisible(value) {
this.setState({modalVisable: value});
}
}
//return中添加模态框组件
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进行提交数据
//阻止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=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("注册成功");
//设置模态框消失
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进行提交数据
//阻止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);
//设置模态框消失
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中
//点击登录表单中的登录按钮,直接设置为登录状态
login(userLogin) {
this.setState({userName: userLogin.userName, hasLogined: true, userId: userLogin.userId});
localStorage.userName = userLogin.userName;
localStorage.userId = userLogin.userId;
}
-
注销功能
Logout组件中的注销登陆按钮绑定了logout事件
header.js中logout事件
//点击MenuItem中退出登录按钮
logout() {
localStorage.userName = '';
localStorage.userId = '';
this.setState({hasLogined: false, userName: '', userId: ''});
};
3.2 头条首页内容区
左边空2列,中间21列,右边1列
中间21列,左边8列,中间10列,右边6列
pc_news_container.js
3.2.1 左边部分实现
pc_news_container中左边架构。
<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实现
向后台发起请求,得到json数据,赋给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/>组件将获得的数据解析显示。
每一条数据包括image、h3、p构成
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布局,对齐方式由父组件传递而来,这里调用时传递的是center。
样式:
.image_news_container{
display:flex;
flex-wrap:wrap;
}
/*图片框每个item的div*/
.image_news_item{
margin: 0.5rem;
}
/*设置图片块每一项的标题*/
.image_news_item h3{
white-space: nowrap;
overflow:hidden;
text-overflow:ellipsis;
}
/*左边imageblock的内边距*/
.image_card .ant-card-body{
padding-left: 0px;
padding-right: 0px;
padding-bottom: 14px;
}
3.2.2 中间部分的实现
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实现
import React from 'react';
import PCNewsComponent from './pc_news_Component';
export default class PCNewsBlock 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;
//看news的长度是否为0,字符串长度为0则是false表示未加载到数据,为其他值则true加载到数据
const newsCard = news.length ?
<PCNewsComponent news={news}/>
: '正在加载';
return (
<div>
{newsCard}
</div>
);
}
}
- PCNewsComponent实现
解析成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 右边部分实现
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构成。
左边宽度固定,右边自适应布局。
使用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='国内新闻' 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.手机端实现
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 头部实现
思路和pc端类似,点击icon显示登陆框,如果已经登录,点击就注销。
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 内容区
- mobile_content.js
使用tab实现,每个tab下由轮播图和新闻块构成。
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='国内' 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 新闻模块实现
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组件
左边固定宽度,右边自适应。
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;
}
- 加载更多功能
当点击加载更多或者已经滑动到底部,就会触发loadMoreFn,去获取后台数据。
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();
}
//添加滚动事件监听
window.addEventListener('scroll',function () {
if(this.props.isLoadingMore)
return;
if(timeoutId)
clearTimeout(timeoutId);
//因为一滚动就会触发事件,我们希望50ms才触发一次
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>
);
}
}