django2.2 简单博客二

紧接简单博客一,主要为前端内容。

博客项目前端开发

前端用react框架。将脚手架react-mobx-starter-master在项目目录前,改名为blog-fe(front-end),在命令中输入npm i装载。
在src中新增component、service、css目录。
先来看看总目录及含义
frontend/
…|–.babelrc # babel相关
…|–.gitignore
…|–.npmrc # register连接
…|–index.html # 测试用的index
…|–jsconfig.json # vs-code用的
…|–LICENSE
…|–package-lock.json # json的包
…|–package.json
…|–README.md
…|–webpack.config.dev.js # react部分讲过
…|–webpack.config.prod.js # 这是部署
…|–src/
…|…|–component/ # react组件
…|…|–service/ #
…|…|–css/ # 要用的样式表放在这里
…|…|–index.html # 模板
…|…|–index.js #
在webpack.config.dev中修改proxy将前端的api接口转到本地的web server端口8000处理,这里django和react是在同一台电脑上,而mysql是在虚拟机上,通过django连接的虚拟机ip地址。因此这里先设置端口即可;

proxy: {
            '/api': {
                target: 'http://127.0.0.1:8000',
                pathRewrite:{'^/api':''},
                changeOrigin: true
            }

这里使用了pathRewrite,如果原路径为 http://127.0.0.1:8000/因为在转接后路径会变成http://127.0.0.1:8000/api/,所以要用pathRewrite将/api转为/。
直接npm i 或者
npm install
npm install react-router
npm install react-router-dom
安装依赖。

前端路由

前端采用react-router组件完成,修改src/index.js

import React from 'react';
import ReactDom from 'react-dom';
import {Route,Link,BrowserRouter as Router} from 'react-router-dom';
import Login from './component/login';
import Reg from './component/reg';


const Home = () =>(
  <div>
    <h2>Home</h2>
  </div>
);

const About = () =>(
  <div>
    <h2>About</h2>
  </div>
);

const App = () =>(
  <Router>
    <div>
      <div>
        <ul>
          <li><Link to='/'>主页</Link></li>
          <li><Link to='/login'>登录</Link></li>
          <li><Link to='/reg'>注册</Link></li>
          <li><Link to='/about'>关于</Link></li>
        </ul>
      </div>
      {/* 静态路由 */}
      <Route exact path='/' component = {Home} />
      <Route path='/about' component = {About} />
      <Route path='/login' component = {Login} />
      <Route path='/reg' component = {Reg} />
    </div>
  </Router>
);

ReactDom.render(<App />,document.getElementById('root'))

Route负责静态路由,我们后期用nginx负责静态路由的转
path是匹配路径,component是目标组件,从文件/component中导入。
其中exact:bool值,true时要求路径完全匹配
strict:bool值,true时要求严格匹配,但是url字符串可以是自己的子串?
地址变化,router组件会匹配路径,然后使用匹配的组件渲染。

登录组件如下,采用模板https://codepen.io/colorlib/pen/rxddKy?q=login&limit=all&type=type-pens,这里使用模板要注意将class属性改为className,并且将替换成组件
如下:

import React from 'react';
import {Link,Redirect} from 'react-router-dom';
import '../css/login.css'; // 导入样本表
import UserService from '../service/user';
import {observer} from 'mobx-react';


const userService = new UserService();

export default class Login extends React.Component{
    render(){
        return <_Login service={userService} />;
    }
}

@observer
class _Login extends React.Component{
    handleClick(event){
        event.preventDefault();

        let fm = event.target.form;
        console.log('~~~~');
        this.props.service.login(
            fm[0].value,fm[1].value
        );
    }
    render() {
        console.log('~~~~~~~~');
        if (this.props.service.loggedin){
            console.log('!!!!!!')
            return <Redirect to='/' />;  // 跳转到根  记得导入
        }

        return (<div className="login-page">
            <div className="form">
                <form className="register-form">
                    <input type="text" placeholder="邮箱"/>
                    <input type="password" placeholder="密码"/>
                    <button onClick={this.handleClick.bind(this)}>登录</button>
                    <p className="message">还没注册?<Link to='/reg'>注册</Link></p>
                </form>
            </div>
        </div>);
    }
}
import React from 'react';
import {Link,Redirect} from 'react-router-dom';
import '../css/login.css';  // 导入样本表login和reg可以用同一个
import UserService from '../service/user';
import {observer} from 'mobx-react';


const userService = new UserService();

@observer
export default class Reg extends React.Component{
    render(){
        return <_Reg service={userService} />;
    }
}

class _Reg extends React.Component{
    handleClick(event){
        event.preventDefault();
        let fm = event.target.form;
        this.props.service.reg(
            fm[0].value,fm[1].value,fm[2].value  // 这里可以判断注册的密码和用户,邮件是否满足,也可以做一个异步调用
        );
        console.log('!~~~~~~~')
    }
    render() {
        if (this.props.service.loggedin){
            console.log('!!!!!')
            return <Redirect to='/' />
        }
        return (<div className="login-page">
            <div className="form">
                <form className="register-form">
                    <input type="text" placeholder="用户名"/>
                    <input type="text" placeholder="邮箱"/>
                    <input type="password" placeholder="密码"/>
                    <input type="password" placeholder="确认密码"/>
                    <button onClick={this.handleClick.bind(this)}>注册</button>
                    <p className="message">Already registered? <Link to='/login'>login</Link></p>
                </form>
            </div>
        </div>);
    }
}

此时还没有使用样式表,需要用到css。
在src/css中,创建login.css,放入内容:

body {
  background: #456;
  font-family: SimSun;
  font-size: 14px;
}

.login-page {
  width: 360px;
  padding: 8% 0 0;
  margin: auto;
}
.form {
  font-family: "Microsoft YaHei", SimSun;
  position: relative;
  z-index: 1;
  background: #FFFFFF;
  max-width: 360px;
  margin: 0 auto 100px;
  padding: 45px;
  text-align: center;
  box-shadow: 0 0 20px 0 rgba(0, 0, 0, 0.2), 0 5px 5px 0 rgba(0, 0, 0, 0.24);
}
.form input {
  outline: 0;
  background: #f2f2f2;
  width: 100%;
  border: 0;
  margin: 0 0 15px;
  padding: 15px;
  box-sizing: border-box;
  font-size: 14px;
}
.form button {
  text-transform: uppercase;
  outline: 0;
  background: #4CAF50;
  width: 100%;
  border: 0;
  padding: 15px;
  color: #FFFFFF;
  font-size: 14px;
  cursor: pointer;
}
.form button:hover,.form button:active,.form button:focus {
  background: #43A047;
}
.form .message {
  margin: 15px 0 0;
  color: #b3b3b3;
  font-size: 12px;
}
.form .message a {
  color: #4CAF50;
  text-decoration: none;
}

在login.js中导入样本表,然后必须要配置css loader,不然会导入失败。
在配置文件webpack.config.dev中增加css loader;

module: {
        rules: [
            {
                test: /\.js$/,
                exclude: /node_modules/,
                use: [
                    { loader: 'react-hot-loader/webpack' },
                    { loader: 'babel-loader' }
                ]
            },
            {
                // 修改完这个后需要重启,这时候更改全局配置已经晚了
                test:/\.css$/,  
                exclude:/node_modules/,
                use:[
                    { loader:'style-loader'},
                    { loader:'css-loader'},
                ]
            },
            {
                test: /\.less$/,
                use: [
                    { loader: "style-loader" },
                    { loader: "css-loader" },
                    { loader: "less-loader" }
                ]
            }
        ]
    },

修改完后重启服务,即可看到效果。注册组件类似。在index.js中增加一个导航栏,方便切换页面。这里已经完成了绝大部分前端的登录代码,测试时要将django打开,数据库连接好,可以看到成功登录,并且写入了Localstorage,实现了跳转。

分层策略

这里博客的前端分为三层:
1、视图层,负责显示数据,每一个react组件一个.js文件,在component文件夹中,这里还有一个配套的样式表css。
2、服务层,负责数据处理逻辑,命名为Service,在service文件夹中
3、model层,负责数据传输,从后端取得数据。

上面的代码实现了登录功能,
view层,登录组件和用户交互,点击button触发onClick,调用事件响应函数handclick,handclick中调用service层的login函数。service层,负责业务逻辑处理,调用Model层数据操作函数。
通过event.preventDefault();阻止了页面刷新,event.target.form返回按钮所在的表单,可以看成一个数组。let fm = event.target.form; fm[0].value,fm[1].value,fm[2].value 通过这个方式可以拿到提交的数据如邮箱和密码。Login可以通过外部使用props注入,从而使用UserService实例。

axios异步库

axios是一个基于Promise的HTTP异步库,可以用在浏览器或者nodejs中。使用axios发起异步调用,完成POST,GET方法的数据提交。可以用npm install axios安装,导入import axios from ‘axios’;官方网站:https://www.kancloud.cn/yunye/axios/234845
将service/user.js修改如下:

import axios from 'axios';
import store from 'store';
import {observable} from 'mobx';


store.addPlugin(require('store/plugins/expire'))

export default class UserService{
    @observable loggedin = false;

    login(email,password){
        console.log(email,password);
        axios.post('/api/user/login',{
            email:email,
            password:password
        }).then(
            response => {
                console.log(response.data);
                console.log(response.status);
                store.set('token',
                response.data.token,
                (new Date()).getTime()+(8*3600*1000));
                this.loggedin = true;
            }
        ).catch(
            function(error){
                console.log(error);
            }
        )
    };}

token持久化–localstorage

我们使用localstorage存储token,这是浏览器端持久化的方案之一,HTML5标准增加的技术。为了储存得到的数据,例如加密后的Json。
如CSDN用的token:
django2.2 简单博客二_第1张图片
这里存储的就是key-value对,存储在不同的域名下,不同浏览器对单个域名下存储数据的长度支持不同,一般不超过2MB。
sessionstorage和localstorage差不多,它是会话级,浏览器关闭,会话结束,数据清除。
indexedDB 一个域一个detable,key-value检索方式,建立在关系型数据模型之上,具有索引表、游标、事务等概念。
store.js是一个兼容所有浏览器的LocalStorage包装器,不需要借助Cookie或者Flash。他会借助浏览器自动选择使用localstorage,globalstorage或者userData来实现本地存储功能。采用npm i store安装。使用前可以测试一下store的使用:

let store = require('store');  // 就像导入模块一样

store.set('user','test111');
console.log(store.get('user'));

store.remove('user')

console.log(store.get('user'))
console.log(store.get('user','a'))

store.set('user',{name:'tste1',age:30});
console.log(store.get('user').name, '~~~~~');

store.set('school',{name:'test2222'});

store.each(function(value,key){
    console.log(key,'-->',value)
});

store.clearAll();

console.log(store.get('user'));

// store.addPlugin(require('store/plugins/expire')); // 过期插件
// store.set('token',res.data.token,(new DataCue()).getTime()+(8*3600*1000));

django2.2 简单博客二_第2张图片

Mobx状态管理

Redux和Mobx,状态管理库。前面的代码上写了。
Redux代码很棒,使用严格定义的函数式编程思想,但是学习曲线陡峭,小项目使用的优势不明显。
Mobx,稳定的库,简单方便,适合中小型项目使用,采用面向对象的方式,容易学习和接受,Mobx和react就像是牛奶加巧克力,奶茶加珍珠。建议查看官网https://mobx.js.org/

Mobx实现了观察者模式,简单的说,观察某个目标,只要目标状态(Obserable)发生了变化,就会通知自己内部注册了的观察者Observer。

状态管理

一个组件的onClicke触发事件响应函数,此函数会调用后台服务。但是后台服务比较耗时间,等处理完,需要引起组件的渲染操作。而组件渲染,要使用改变组件的props或者state。
可以采用:
1、同步调用
同步调用中,就是等着耗时的函数返回,可以使用死循环的方式模拟同步调用。
2、异步调用
异步调用有两种思路。
①使用setTimeout
但是这个有两个问题:无法向内部的待执行函数传入参数,比如传入Root实例;延时执行的函数的返回值无法取到,所以无法通知Root
②Promise异步执行
Promise异步执行,如果成功则调用回调函数。不管render中是否显示state的值,只要state改变,都会触发render执行。

import React from 'react'
import ReactDom from 'react-dom'

class Service{
    handle(obj){
        new Promise((resolve,reject) => {
            setTimeout(()=>resolve('ok'), 5000);
        }).then(
            value => {
                obj.setState({ret:(Math.random()*100));
            }
        )
    }
}

class Root extends React.Component {
    state = {ret:null}
    handleClick(event){
        this.props.service.handle(this);
    }

    render(){
        console.log('!!!!!!!!!!!');
        return (
            <div>
                <button onClick={this.handleClick.bind(this)}>触发函数</button><br />
    <span style={{color:'red'}}>{new Date().getTime()} service中修改的值时{this.state.ret}</span>
            </div>        
        )
    }
}


ReactDom.render(<Root service={new Service()} />,document.getElementById('root'))

3、Mobx实现
observable装饰器:设置被观察者
observer装饰器:设置观察者,将react组件转换为响应式组件。
service中被观察者ret变化,导致观察者调用render函数。如果将{this.props.service.ret}注释掉,被观察者变化不引起渲染,render

import React from 'react'
import ReactDom from 'react-dom'
import {observable} from 'mobx'
import {observer} from 'mobx-react'


class Service{
    @observable ret=-100
    handle(obj){
        new Promise((resolve,reject) => {
            setTimeout(()=>resolve('ok'), 5000);
        }).then(
            value => {
                obj.setState({ret:(Math.random()*100));
            }
        )
    }
}

@observer // 将react组件转换为响应式组件
class Root extends React.Component {
    // state = {ret:null} // 不能使用state
    handleClick(event){
        // 异步不能直接使用返回值
        this.props.service.handle(this);
    }

    render(){
        console.log('!!!!!!!!!!!');
        return (
            <div>
                <button onClick={this.handleClick.bind(this)}>触发函数</button><br />
    <span style={{color:'red'}}>{new Date().getTime()} service中修改的state值是{this.props.service.ret}</span>
            </div>        
        )
    }
}


ReactDom.render(<Root service={new Service()} />,document.getElementById('root'))

注册功能实现

在service/user.js中加入reg的代码:

import axios from 'axios';
import store from 'store';
import {observable} from 'mobx';


store.addPlugin(require('store/plugins/expire'))

export default class UserService{
    @observable loggedin = false;

    login(email,password){
        console.log(email,password);
        axios.post('/api/user/login',{
            email:email,
            password:password
        }).then(
            response => {
                console.log(response.data);
                console.log(response.status);
                store.set('token',
                response.data.token,
                (new Date()).getTime()+(8*3600*1000));
                this.loggedin = true;
            }
        ).catch(
            function(error){
                console.log(error);
            }
        )
    };

    reg(name,email,password){
        console.log(email,password);
        axios.post('/api/user/reg',{
            email:email,
            password:password,
            name:name,
        }).then(
            response => {
                console.log(response.data);
                console.log(response.status);
                store.set('token',
                response.data.token,
                (new Date()).getTime()+(8*3600*1000));
                this.loggedin = true;
            }
        ).catch(
            function(error){
                console.log(error);
            }
        )
    }
}

reg组件类和之前的相同。

Ant Design
蚂蚁金服开源的react ui 库。用npm安装即可。
使用方法:

import {List} from 'antd';
import 'antd/lib/list/style/css';

ReactDOM.render(<List />,mountNode);

信息显示

网页开发中,需要很多信息提示,目前信息都是在控制台中输出(可以放在div中,隐藏显示。可以用alert,但是这样不美观,并且用户使用感觉很不好),我们使用Antd的message组件显示友好信息提示。(自学antd)
只要有webpack,支持服务器端渲染即在node.js里用
在service/user.js中增加一个被观察对象:

@observable errMsg = '';

axios.post('/api/user/login',{
            email:email,
            password:password
        }).then(
            response => {
                console.log(response.data);
                console.log(response.status);
                store.set('token',
                response.data.token,
                (new Date()).getTime()+(8*3600*1000));
                this.loggedin = true;
            }
        ).catch(
            // 不能使用function(erro)了
            error=>{
                console.log(error);
                this.errMsg = '登录失败';}
                )

在component/login.js的render中加入:

if (this.props.service.errMsg){
            message.info(this.props.service.errMsg, 3,
                ()=> setTimeout(()=>this.props.service.errMsg = ''),1000)
        }

即可实现消息提醒,而不是用弹出框。
使用高阶装饰器,将组件进一步简化。
部分代码

import {inject} from '../utils'


const service = new UserService();

// export default class Login extends React.Component{
//     render(){
//         return <_Login service={userService} />;
//     }
// }

@inject({service})  // 生成{service:service}对象
@observer // 必须要紧挨着被修饰对象
// class _Login extends React.Component{
export default class Login extends React.Component{
    handleClick(event){
        event.preventDefault();
        ...

utils

import React from 'react';

const inject = obj => Comp => props => <Comp {...obj} {...props}/>;

export {inject};

导航菜单

菜单栏的模板采用antd的布局:
将index.js修改如下

import React from 'react';
import ReactDom from 'react-dom';
import {Route,Link,BrowserRouter as Router} from 'react-router-dom';
import Login from './component/login';
import Reg from './component/reg';
import {Menu, Icon, Layout} from 'antd'; //导入菜单,图标,布局
// import Pub from './component/pub'

import 'antd/lib/layout/style'
import 'antd/lib/menu/style'
import 'antd/lib/icon/style'

const {Header, Content, Footer} = Layout;  //上中下布局

const Home = () =>(
  <div>
    <h2>Home</h2>
  </div>
);

const About = () =>(
  <div>
    <h2>About</h2>
  </div>
);

const App = () =>(
  <Router>
    <Layout>
      <Header>
        <Menu mode='horizontal' theme='dark'>
          <Menu.Item key='home'><Link to='/'><Icon type='home' />主页</Link></Menu.Item>
          <Menu.Item key='login'><Link to='/login'><Icon type='login' />登录</Link></Menu.Item>
          <Menu.Item key='reg'><Link to='/reg'>注册</Link></Menu.Item>
          <Menu.Item key='about'><Link to='/about'><Icon type="appstore" />关于</Link></Menu.Item>
        </ Menu>
      </Header>
      <Content style={{padding:'8px 50px'}}>
      <div style={{background:'#fff',padding:50,minHeight:1000}}>
      {/* 静态路由 */}
      <Route exact path='/' component = {Home} />
      <Route path='/about' component = {About} />
      <Route path='/login' component = {Login} />
      <Route path='/reg' component = {Reg} />
    </div>
    </Content>
    <Footer style={{textAlign:'center'}}>
      需知
    </Footer>
    </Layout>
  </Router>
);

ReactDom.render(<App />,document.getElementById('root'))

这里可以有一些简单的样式和图标。在antd中可以选择很多样式和图标,这里只做了一部分。

博文

/post/pub POST 提交博文的title,content,成功则返回Json,和post_id
/post/id GET 返回博文详情,返回Json,post_id、title、author、author_id、postdate、content
/post/ GET 返回博文列表

业务层

写业务层代码,和之前一样,先在service中创建post.js文件,新建PostService类。

import axios from 'axios';
import {observable} from 'mobx';
import store from 'store';



export default class PostService{
    constructor(){
        // 创建自定义实例,可以增加请求header
        this.axios = axios.create({
            baseURL:'/api/post/'  //加了api才能被代理,创建了一个实例axios,类似于
        })
    }

    @observable msg='';
    @observable posts = [];
    @observable pagination = {page:1,size:20,pages:0,count:0} // 分页信息

    getJWT(){
        return store.get('token',null);
    }

    pub (title,content){
        console.log(title);
        axios.post('put',{title,content},
        {headers:{'JWT':this.getJWT()}} 
        // dev server 会代理
        ).then(
            response=>{
                console.log(response.data);
                console.log(response.status);
                this.msg = '博文提交成功' // 信息提示
            }
        ).catch(
            error=>{
                console.log(error);
                this.msg='登录失败';
            }
        )
    }

    list(search){
        this.axios.get(search
            ).then(
                response=>{
                    console.log(response.data)
                    console.log(response.status)
                    this.posts = response.data.posts
                    this.pagination = response.data.pagination
                    
                }
            ).catch(
                error=>{
                    console.log(error);
                    this.msg = '加载失败'  //信息提示
                }
            )
    }
}

header中的JWT,与后台django server 通信,身份认证需要JWT,这个放在request header中,使用axios的API与后端连接。

文章列表组件
创建component/list.js,创建List组件,在index.js中提交菜单和路由。

import React from 'react';
import {observable, observer} from 'mobx-react';
import { message,Icon } from 'antd';
import {inject,parse_qs} from '../utils';
import {List} from 'antd';
import {Link} from 'react-router-dom';
import PostService from '../service/post';

import 'antd/lib/message/style';
import 'antd/lib/list/style';



const service = new PostService();


@inject({service})
@observer
export default class L extends React.Component{
    constructor(props){
        super(props);
        props.service.list(props.location.search);
        console.log(1,props)
        // Object{service:PostService,match:Object,Location:Object,history:Object,staticContext:undefined}
        // 在浏览器中查看有很多属性,可以拿到
        // const {location:{search}=props;}
    }
    handleChange(pageNo,pageSize){
        console.log(2,pageNo,pageSize)
        // 重新拼接查询字符串,向后传递
        let search = '?page='+pageNo+'&size='+pageSize;
        this.props.service.list(search);
        // window.location.href='/list' + search //这里相当于在地址栏输入'/list' + search,然后转到这个地址中
    }

    geturl(c){
        let obj = parse_qs(this.props.location.search)
        let {size=20} = obj;
        return '/list?page=' +c +'&size='+size;
    }

    itemRender(current,type,originalElement){
        if (current == 0)return originalElement;

        if (type === 'page')
            return <Link to={this.geturl(current)}>{current}</Link>;
        if (type === 'next')
            return <Link to={this.geturl(current)} className='ant-pagination-item-link' ><Icon type="right" /></Link>;
        if (type === 'prev')
            return <Link to={this.geturl(current)} className='ant-pagination-item-link'><Icon type="left" /></Link>;
        
        console.log(3,originalElement);
        return originalElement;
    }



    render(){
        let data = this.props.service.posts;
        if (data.length){
            const pagination = this.props.service.pagination;

            return (
                <List bordered={true} dataSource={data} renderItem={
                    // bordered有边线,dataSource给定数据源,renderItem渲染每一行
                item=>(<List.Item>

                <List.Item.Meta title={<Link to={'/post/'+ item.post_id}>{item.title}</Link>} />
                {/* 这里用的是/post/+id所以index里用的是id匹配, */}

                </ List.Item>)
                // List.Item 每一行的组件 可以使用Link增加连接
                    // item=>( 也可以这样更加复杂
                    //      
                    //     {item.title} //详情页连接
                    // } />
                    // )
                }
                pagination={{
                    current:pagination.page,
                    pageSize:pagination.size,
                    total:pagination.count,
                    onChange:this.handleChange.bind(this),
                    itemRender:this.itemRender.bind(this) // 别忘了要修改dom
                }}
                />
            )
        } else {
            return (<div></div>)
        }
    }
}

这里用户请求的URL是/list?page=?,转换成/api/post/?page=?,需要提取查询字符串。
前端路由由react-router管理,匹配路径后才会路由,这里提供了匹配项,将匹配的数据注入组件的props中,也可以使用解构提取const{match,location}=this.props
location是一个对象,其中pathname表示路径,search表示查询字符串。
拿到字符串后可以用URLSearchParams解析它,但是它是实验性的,不建议用在生产中使用,这里将查询字符串直接拼接发往后端,由django服务器判断。

var params = new URLSearchParams(url.search);
console.log(params.get('page'),params.get('size'))
list组件

antd的list要用到3.X版本,需要升级,将package.json版本信息改成^3.1.5。然后npm update。
代码如下:

import React from 'react';
import {observable, observer} from 'mobx-react';
import { message } from 'antd';
import {inject} from '../utils';
import {List} from 'antd';
import {Link} from 'react-router-dom';
import PostService from '../service/post';

import 'antd/lib/message/style';
import 'antd/lib/list/style';



const service = new PostService();


@inject({service})
@observer
export default class L extends React.Component{
    constructor(props){
        super(props);
        props.service.list(props.location.search);
    }
    render(){
        let data = this.props.service.posts;
        if (data.length){
            return (
                <List bordered={true} dataSource={data} renderItem={
                    // bordered有边线,dataSource给定数据源,renderItem渲染每一行
                item=>(<List.Item>
                    <Link to={'/post/' + item.post_id}>
                        {item.title}
                    </Link>
                </List.Item>)
                // List.Item 每一行的组件 可以使用Link增加连接
                    // item=>( 也可以这样更加复杂
                    //      
                    //     {item.title} //详情页连接
                    // } />
                    // )
                }/>
                
            )
        } else {
            return (<div></div>)
        }
    }
}

postservice代码如下:

import axios from 'axios';
import {observable} from 'mobx';
import store from 'store';



export default class PostService{
    constructor(){
        // 创建自定义实例,可以增加请求header
        this.axios = axios.create({
            baseURL:'/api/post/'
        })
    }

    @observable msg='';
    @observable posts = [];
    @observable pagination = {page:1,size:20,pages:0,count:0} // 分页信息

    getJWT(){
        return store.get('token',null);
    }

    pub (title,content){
        console.log(title);
        axios.post('put',{title,content},
        {headers:{'JWT':this.getJWT()}} 
        // dev server 会代理
        ).then(
            response=>{
                console.log(response.data);
                console.log(response.status);
                this.msg = '博文提交成功' // 信息提示
            }
        ).catch(
            error=>{
                console.log(error);
                this.msg='登录失败';
            }
        )
    }

    list(search){
        this.axios.get(search
            ).then(
                response=>{
                    console.log(response.data)
                    console.log(response.status)
                    this.posts = response.data.posts
                    this.pagination = response.data.pagination
                    
                }
            ).catch(
                error=>{
                    console.log(error);
                    this.msg = '加载失败'  //信息提示
                }
            )
    }
}
分页功能

分页也需要解析查询字符串,因此写一个解析函数,放入utils.js中
分页采用了Pagination组件,在L组件的render函数的List
组件中使用pagination属性,这个属性内放入一个pagination对象,属性有;current:当前页;pagesize:页面内行数;total:总行数;onchange:页码切换时调用回调函数为(pageNo,pageSize)=>{},切换是获得当前页码和页内行数。
将list.js修改。

import React from 'react';
import {observable, observer} from 'mobx-react';
import { message } from 'antd';
import {inject,parse_qs} from '../utils';//记得要导出export
import {List} from 'antd';
import {Link} from 'react-router-dom';
import PostService from '../service/post';

import 'antd/lib/message/style';
import 'antd/lib/list/style';



const service = new PostService();


@inject({service})
@observer
export default class L extends React.Component{
    constructor(props){
        super(props);
        props.service.list(props.location.search);
        console.log(1,props)
        // Object{service:PostService,match:Object,Location:Object,history:Object,staticContext:undefined}
        // 在浏览器中查看有很多属性,可以拿到
        // const {location:{search}=props;}
    }
    handleChange(pageNo,pageSize){
        console.log(2,pageNo,pageSize)
        // 重新拼接查询字符串,向后传递
        let search = '?page='+pageNo+'&size='+pageSize;
        this.props.service.list(search);
        // window.location.href='/list' + search //这里相当于在地址栏输入'/list' + search,然后转到这个地址中
    }

    geturl(c){
        let obj = parse_qs(this.props.location.search)
        let {size=20} = obj;
        return '/list?page=' +c +'&size='+size;
    }

    itemRender(current,type,originalElement){
        if (current == 0)return originalElement;

        if (type === 'page')
            return <Link to={this.geturl(current)}>{current}</Link>;
        if (type === 'next')
            return <Link to={this.geturl(current)} className='ant-pagination-item-link' ><Icon type="right" /></Link>;
        if (type === 'prev')
            return <Link to={this.geturl(current)} className='ant-pagination-item-link'><Icon type="left" /></Link>;
        
        console.log(3,originalElement);
        return originalElement;
    }



    render(){
        let data = this.props.service.posts;
        if (data.length){
            const pagination = this.props.service.pagination;

            return (
                <List bordered={true} dataSource={data} renderItem={
                    // bordered有边线,dataSource给定数据源,renderItem渲染每一行
                item=>(<List.Item>

                <List.Item.Meta title={<Link to={'/post/'+ item.post_id}>{item.title}</Link>} />

                </ List.Item>)
                // List.Item 每一行的组件 可以使用Link增加连接
                    // item=>( 也可以这样更加复杂
                    //      
                    //     {item.title} //详情页连接
                    // } />
                    // )
                }
                pagination={{
                    current:pagination.page,
                    pageSize:pagination.size,
                    total:pagination.count,
                    onChange:this.handleChange.bind(this),
                    itemRender:this.itemRender.bind(this) // 别忘了要修改dom
                }}
                />
            )
        } else {
            return (<div></div>)
        }
    }
}

这里有一个地址栏不变的问题,即无论怎么换页,URL都不会动,不能一致,这里需要定义itemRender属性,定义一个函数,这个函数有三个参数:
current:当前的pagenumber
type:当前的type,上一页为prev,下一页为next,页码为page
originalElement,直接返回就可以,这个参数含义。

详情页组件

点击博客详情页后url是list.js中的link为post+id(前面采用的是拼接字符串实现),因此这里要将index.js的静态路由改为:

<Route path='/post/:id' component = {Post} /> 

这样可以将路由转到post组件,并且传入id。
post组件如下:

import React from 'react';
import {observable, observer} from 'mobx-react';
import { message } from 'antd';
import {inject} from '../utils';
import {Card} from 'antd';
import {Link} from 'react-router-dom';
import PostService from '../service/post';

import 'antd/lib/message/style';
import 'antd/lib/card/style';

const service = new PostService();

@inject({service})
@observer
export default class Post extends React.Component{
    constructor(props){
        super(props);
        // console.log('post',props)
        // props:etc,match:id,path,url,etc
        let {id = -1} = props.match.params;
        // console.log(props.service)
        this.props=props.service.getpost(id);
        // console.log(this.props)
    }

    render(){
        let s = this.props.service;
        if (s.msg){
            message.info(s.msg, 3, ()=> setTimeout(()=>s.msg='',5000));
        }
        let post = s.post;
        console.log('this.post from post.js com',s);
        
        if (post.title){
            return <Card title={post.title} bordered={true} style={{width:600}}>
        <p>{post.author} {new Date(post.postdata*1000).toLocaleDateString()}</p>
        <p>{post.content}</p>
            </Card>
        } else 
            return (<div></div>);
    }
}

这里点击文章标题即可转到详情页面。接下来是nginx部署

你可能感兴趣的:(博客)