11.React实战开发--适配PC及移动端的新闻头条网站

github:https://github.com/Ching-Lee/react_news

1.环境搭建

1.1 使用react脚本架搭建项目(详情参见第一节)

创建项目:
进入项目目录执行如下命令。

create-react-app react-news
11.React实战开发--适配PC及移动端的新闻头条网站_第1张图片

11.React实战开发--适配PC及移动端的新闻头条网站_第2张图片

进入项目目录

cd react-news
npm start
11.React实战开发--适配PC及移动端的新闻头条网站_第3张图片

11.React实战开发--适配PC及移动端的新闻头条网站_第4张图片
3000端口

清除App.js中的内容

import React, { Component } from 'react';

import './App.css';

class App extends Component {
  render() {
    return (
     

Init

); } } export default App;
11.React实战开发--适配PC及移动端的新闻头条网站_第5张图片
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 (
        

Init

); } } export default App;
  • 修改App.css,引入antd的css样式
@import '~antd/dist/antd.css';

.App {
  text-align: center;
}
....
  • 说明引入成功


    11.React实战开发--适配PC及移动端的新闻头条网站_第6张图片
    效果如图

2.路由配置

npm install [email protected] --save

在app.js中使用媒体查询配置路由,大于1224加载PCAPP组件,小于1224的设备加载MobileApp组件

npm install react-responsive --save
class App extends Component {
    render() {
        return (
          
); } }

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(
            
{this.props.children}
); } }

3.1 头部组件的实现


  • 使用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 (
            
logo 新闻头条
); } }
  • 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 ?
            
                
             :
            
                注册/登录
            ;

        return(
            
                
                    
                        头条
                    
                

                
                    
                        社会
                    
                

                
                    
                        国内
                    
                

                
                    
                        国际
                    
                

                
                    
                        娱乐
                    
                

                
                    
                        体育
                    
                

                
                    
                        科技
                    
                

                
                    
                        时尚
                    
                
                {userShow}
            

        );
    }
}
  • 其中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 (
            
  
); } } //设置必须需要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(){
    ...
 
...
 
 
}
  • LoginRegisterModal组件


    11.React实战开发--适配PC及移动端的新闻头条网站_第7张图片

    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(

            
                
                    
                        
                    
                    
                        
                    

                
            
        );
    }
}
  • 注册tab中包裹了注册表单
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 (
            
{getFieldDecorator('r_userName', { rules: [{required: true, message: '请输入您的账户!'}], }) (} placeholder='请输入您的账户'/>)} {getFieldDecorator('r_password', { rules: [{required: true, message: '请输入您的密码'}, { validator: this.checkConfirm.bind(this), }], })( } type='password' placeholder='请输入您的密码'/>)} {getFieldDecorator('r_confirmPassword', { rules: [{ required: true, message: '请确认您的密码!', }, { validator: this.checkPassword.bind(this), }], })( } type='password' placeholder='请再次输入您的密码'/> )}
); } } const WrappedRegisterForm = Form.create()(RegisterForm); export default WrappedRegisterForm;
  • 登录功能

登录tab中包裹了登录表单
组件

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 (
            
{getFieldDecorator('userName', { rules: [{ required: true, message: 'Please input your username!' }], })( } placeholder="Username"/> )} {getFieldDecorator('password', { rules: [{ required: true, message: 'Please input your Password!' }], })( } type="password" placeholder="Password"/> )} {getFieldDecorator('remember', { valuePropName: 'checked', initialValue: true, })( Remember me )} {this.state.hasUser}
); } } 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 头条首页内容区

11.React实战开发--适配PC及移动端的新闻头条网站_第8张图片

11.React实战开发--适配PC及移动端的新闻头条网站_第9张图片

左边空2列,中间21列,右边1列
中间21列,左边8列,中间10列,右边6列
pc_news_container.js


11.React实战开发--适配PC及移动端的新闻头条网站_第10张图片
3.2.1 左边部分实现

pc_news_container中左边架构。

 
       

       
            
                   
                         

其中Carousel是轮播图插件。

  • PCNewsImageBlock实现


    11.React实战开发--适配PC及移动端的新闻头条网站_第11张图片
11.React实战开发--适配PC及移动端的新闻头条网站_第12张图片

向后台发起请求,得到json数据,赋给this.state.news。
传送给组件。

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 ?
            
            : '正在加载';
        return (
            
{newsImage}
); } }
  • 组件将获得的数据解析显示。
    每一条数据包括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) => (
            
newsItem.title

{newsItem.title}

{newsItem.author_name}

)); return(
{newsList}
); } }

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

 
      
  • PC_news_block实现


    11.React实战开发--适配PC及移动端的新闻头条网站_第13张图片

    11.React实战开发--适配PC及移动端的新闻头条网站_第14张图片
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 ?
            
            : '正在加载';

        return (
            
{newsCard}
); } }
  • 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) => (
            
  • {newsItem.title}
  • )); return (
      {newsList}
    ); } }

    3.2.3 右边部分实现

    11.React实战开发--适配PC及移动端的新闻头条网站_第15张图片

    pc_news_container

    
        
    11.React实战开发--适配PC及移动端的新闻头条网站_第16张图片
    • 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?
              
                :'正在加载';
    
            return(
                
    {newsList}
    ); } }
    • 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)=>(
                
                    
    {newsItem.title}/

    {newsItem.title}

    {newsItem.realtype} {newsItem.author_name}
    )); return( {newsList} ); } }

    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 最下面部分

    11.React实战开发--适配PC及移动端的新闻头条网站_第17张图片
     
        
        
    
    

    3.3.详情页

    11.React实战开发--适配PC及移动端的新闻头条网站_第18张图片

    11.React实战开发--适配PC及移动端的新闻头条网站_第19张图片
    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 (
    
                
    ); } }

    3.4.页脚

    import React from 'react';
    import {Row,Col} from 'antd';
    
    
    export default class PCFooter extends React.Component{
        constructor(props){
            super(props);
        }
        render(){
            return (
                
    © 2018 新闻头条。All Rights Reserved.
    ); } }

    4.手机端实现


    11.React实战开发--适配PC及移动端的新闻头条网站_第20张图片

    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 (
                
    ); } }

    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 ?
                 : ;
            return (
                
    mobile_logo 新闻头条 {userShow}
    ); } }

    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(
              
                  
                      
    ); } }
    • 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 ?
               
                : '正在加载';
    
            return (
                
    {newsList}
    ); } }
    • 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) => (
                
                   
    {newsItem.title}

    {newsItem.title}

    {newsItem.realtype} {newsItem.author_name}
    )); return(
    {newlist}
    ); } }

    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 (
                
    {newsList} { this.state.hasMore? :
    木有更多咯
    }
    );

    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
                    {
                        this.props.isLoadingMore?
                            加载中...
                            :加载更多
                    }
                
    ); } }

    你可能感兴趣的:(11.React实战开发--适配PC及移动端的新闻头条网站)