紧接简单博客一,主要为前端内容。
前端用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是一个基于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);
}
)
};}
我们使用localstorage存储token,这是浏览器端持久化的方案之一,HTML5标准增加的技术。为了储存得到的数据,例如加密后的Json。
如CSDN用的token:
这里存储的就是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));
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'))
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部署