11.React實戰(zhàn)開發(fā)--適配PC及移動端的新聞頭條網(wǎng)站

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

3000端口

清除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>
                &nbsp;&nbsp;
                <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'}}>
                        &copy;&nbsp;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>
        );
    }
}
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末碰煌,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子绅作,更是在濱河造成了極大的恐慌芦圾,老刑警劉巖,帶你破解...
    沈念sama閱讀 221,331評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件俄认,死亡現(xiàn)場離奇詭異个少,居然都是意外死亡,警方通過查閱死者的電腦和手機梭依,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,372評論 3 398
  • 文/潘曉璐 我一進店門稍算,熙熙樓的掌柜王于貴愁眉苦臉地迎上來典尾,“玉大人役拴,你說我怎么就攤上這事〖毓。” “怎么了河闰?”我有些...
    開封第一講書人閱讀 167,755評論 0 360
  • 文/不壞的土叔 我叫張陵,是天一觀的道長褥紫。 經(jīng)常有香客問我姜性,道長,這世上最難降的妖魔是什么髓考? 我笑而不...
    開封第一講書人閱讀 59,528評論 1 296
  • 正文 為了忘掉前任部念,我火速辦了婚禮,結(jié)果婚禮上氨菇,老公的妹妹穿的比我還像新娘儡炼。我一直安慰自己,他們只是感情好查蓉,可當我...
    茶點故事閱讀 68,526評論 6 397
  • 文/花漫 我一把揭開白布乌询。 她就那樣靜靜地躺著,像睡著了一般豌研。 火紅的嫁衣襯著肌膚如雪妹田。 梳的紋絲不亂的頭發(fā)上唬党,一...
    開封第一講書人閱讀 52,166評論 1 308
  • 那天,我揣著相機與錄音鬼佣,去河邊找鬼驶拱。 笑死,一個胖子當著我的面吹牛晶衷,可吹牛的內(nèi)容都是我干的屯烦。 我是一名探鬼主播,決...
    沈念sama閱讀 40,768評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼房铭,長吁一口氣:“原來是場噩夢啊……” “哼驻龟!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起缸匪,我...
    開封第一講書人閱讀 39,664評論 0 276
  • 序言:老撾萬榮一對情侶失蹤翁狐,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后凌蔬,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體露懒,經(jīng)...
    沈念sama閱讀 46,205評論 1 319
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,290評論 3 340
  • 正文 我和宋清朗相戀三年砂心,在試婚紗的時候發(fā)現(xiàn)自己被綠了懈词。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,435評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡辩诞,死狀恐怖坎弯,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情译暂,我是刑警寧澤抠忘,帶...
    沈念sama閱讀 36,126評論 5 349
  • 正文 年R本政府宣布,位于F島的核電站外永,受9級特大地震影響崎脉,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜伯顶,卻給世界環(huán)境...
    茶點故事閱讀 41,804評論 3 333
  • 文/蒙蒙 一囚灼、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧祭衩,春花似錦灶体、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,276評論 0 23
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至劫乱,卻和暖如春织中,著一層夾襖步出監(jiān)牢的瞬間锥涕,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,393評論 1 272
  • 我被黑心中介騙來泰國打工狭吼, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留层坠,地道東北人。 一個月前我還...
    沈念sama閱讀 48,818評論 3 376
  • 正文 我出身青樓刁笙,卻偏偏與公主長得像破花,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子疲吸,可洞房花燭夜當晚...
    茶點故事閱讀 45,442評論 2 359

推薦閱讀更多精彩內(nèi)容