配置项目
1.1 安装脚手架 [#](#t11.1 安装脚手架)
cnpm install -g create-react-app yarn
1.2 新建项目 [#](#t21.2 新建项目)
create-react-app zhangximufengblog
cd zhangximufengblog
cnpm start
1.3 安装antd [#](#t31.3 安装antd)
cnpm i antd -S
1.4 使用antd [#](#t41.4 使用antd)
import Button from 'antd/lib/button';
@import '~antd/dist/antd.css';
<div className="App">
<Button>Button</Button>
</div>
1.5 按需加载 [#](#t51.5 按需加载)
默认配置进行自定义,react-app-rewired(一个对 create-react-app 进行自定义配置的社区解决方案)
1.5.1 安装 react-app-rewired [#](#t61.5.1 安装 react-app-rewired)
$ cnpm i react-app-rewired -S
1.5.2 修改命令 [#](#t71.5.2 修改命令)
/ package.json /
"scripts": {
- "start": "react-scripts start",
+ "start": "react-app-rewired start",
- "build": "react-scripts build",
+ "build": "react-app-rewired build",
- "test": "react-scripts test --env=jsdom",
+ "test": "react-app-rewired test --env=jsdom",
}
1.5.3 config-overrides.js [#](#t81.5.3 config-overrides.js)
创建一个 config-overrides.js 用于修改默认配置。
module.exports = function override(config, env) {
// do stuff with the webpack config...
return config;
};
1.5.4 babel-plugin-import [#](#t91.5.4 babel-plugin-import)
babel-plugin-import 是一个用于按需加载组件代码和样式的 babel 插件,现在我们尝试安装它并修改 config-overrides.js 文件。
cnpm i babel-plugin-import -D
+ const { injectBabelPlugin } = require('react-app-rewired');
module.exports = function override(config, env) {
+ config = injectBabelPlugin(['import', { libraryName: 'antd', libraryDirectory: 'es', style: 'css' }], config);
return config;
};
1.5.5 自定义主题 [#](#t101.5.5 自定义主题)
cnpm i react-app-rewire-less -D
const { injectBabelPlugin } = require('react-app-rewired');
+ const rewireLess = require('react-app-rewire-less');
module.exports = function override(config, env) {
- config = injectBabelPlugin(['import', { libraryName: 'antd', style: 'css' }], config);
+ config = injectBabelPlugin(['import', { libraryName: 'antd', style: true }], config);
+ config = rewireLess.withLoaderOptions({
+ modifyVars: { "@primary-color": "#1DA57A" },
+ })(config, env);
return config;
};
#
2.跑通路由cnpm i react-router-dom -S
\src\index.js
import React from 'react';
import ReactDOM from 'react-dom';
import './style/common.less'
import Routers from './router'
ReactDOM.render(<Routers />, document.getElementById('root'));
\src\router.js
import React from 'react'
import { Switch, Route, HashRouter as Router, Redirect } from 'react-router-dom'
function Login() {
return <h1>Login</h1>;
}
function Admin() {
return <h1>Admin</h1>;
}
export default class Routers extends React.Component {
render() {
return (
<Router>
<Switch>
<Route exact path='/' component={Login} />
<Route path='/admin' component={Admin} />
<Redirect to='/' />
</Switch>
</Router>
)
}
}
3. 编写注册页 [#](#t123. 编写注册页)
3.1 编写注册主页 [#](#t133.1 编写注册主页)
import React, { Component } from 'react';
import { Form, Input, Button, Icon } from 'antd';
import user from '../../service/user';
export default class Home extends Component {
handeSubmit = (data) => {
user.signup(data).then(data => {
this.props.history.push('/admin');
});
}
render() {
return (
<div className="login-page">
<div className="login-content">
<h1 className="title">沐枫博客</h1>
<WrappedUserForm onSubmit={this.handeSubmit} />
</div>
</div>
)
}
}
class UserForm extends Component {
checkUsername = (rule, value, callback) => {
if (!value) {
callback('请输入用户名!');
} else if (!/^1\d{10}$/.test(value)) {
callback('用户名必须是手机号');
} else {
callback();
}
}
render() {
const { getFieldDecorator } = this.props.form;
return (
<Form onSubmit={() => this.props.onSubmit(this.props.form.getFieldsValue())} className="login-form" >
<Form.Item>
{
getFieldDecorator('username', {
rules: [{ required: true, messsage: '请输入用户名' }, { validator: this.checkUsername }]
})(<Input prefix={<Icon type="user" style={{ color: 'rgba(0,0,0,.25)' }} />} placeholder="用户名" />)
}
</Form.Item>
<Form.Item>
{
getFieldDecorator('password', {
rules: [{ required: true, messsage: '请输入密码' }]
})(<Input prefix={<Icon type="lock" style={{ color: 'rgba(0,0,0,.25)' }} />} placeholder="密码" />)
}
</Form.Item>
<Form.Item>
{
getFieldDecorator('email', {
rules: [{ type: 'email' }, { required: true, messsage: '请输入邮箱' }]
})(<Input prefix={<Icon type="mail" style={{ color: 'rgba(0,0,0,.25)' }} />} placeholder="邮箱" />)
}
</Form.Item>
<Form.Item>
<Button type="primary" htmlType="submit" className="login-form-button" >注册</Button>
</Form.Item>
</Form>
)
}
}
const WrappedUserForm = Form.create()(UserForm);
3.2 编写注册样式 [#](#t143.2 编写注册样式)
*{
margin: 0;
padding: 0;
}
ul,li{
list-style: none;
}
a,a:hover{
text-decoration: none;
}
html,body,#root{
height:100%;
width:100%;
}
.login-page{
width:100%;
height:100%;
background-color:skyblue;
overflow: hidden;
.login-content{
width:450px;
height:450px;
margin:150px auto;
.title{
text-align: center;
}
}
}
3.3 编写服务端 [#](#t153.3 编写服务端)
index.js
import axios from 'axios';
const baseURL = 'http://localhost:7001';
const config = {
timeout: 8000,
baseURL,
withCredential: true
}
export function get(url) {
return axios({ ...config, url, method: 'get' }).then(response => response.data)
}
export function post(url, data) {
return axios({ ...config, url, data, method: 'post' }).then(response => response.data)
}
export function put(url, data) {
return axios({ ...config, url, data, method: 'put' }).then(response => response.data)
}
export function del(url, data) {
return axios({ ...config, url, data, method: 'delete' }).then(response => response.data)
}
user.js
import { get, post } from './index';
function signup(data) {
return post('/api/users/signup', data);
}
function signin(data) {
return post('/api/users/signin', data);
}
function signout() {
return get('/api/users/signout');
}
export default {
signin,
signup,
signout
}
4. 编写登录功能 [#](#t164. 编写登录功能)
4.1 home组件 [#](#t174.1 home组件)
import React, { Component } from 'react';
import { Form, Input, Button, Icon, message } from 'antd';
import user from '../../service/user';
export default class Home extends Component {
handeSubmit = (isSignUp, data) => {
user[isSignUp ? 'signup' : 'signin'](data).then(data => {
if (data.code == 0)
this.props.history.push('/admin');
else
message.error(data.error.toString());
});
}
render() {
return (
<div className="login-page">
<div className="login-content">
<h1 className="title">沐枫博客</h1>
<WrappedUserForm onSubmit={this.handeSubmit} />
</div>
</div>
)
}
}
class UserForm extends Component {
constructor(props) {
super(props);
this.state = { isSignUp: true };//是否是注册表单
}
checkUsername = (rule, value, callback) => {
if (!value) {
callback('请输入用户名!');
} else if (!/^1\d{10}$/.test(value)) {
callback('用户名必须是手机号');
} else {
callback();
}
}
render() {
const { getFieldDecorator } = this.props.form;
return (
<Form onSubmit={() => this.props.onSubmit(this.state.isSignUp, this.props.form.getFieldsValue())} className="login-form" >
<Form.Item>
{
getFieldDecorator('username', {
rules: [{ required: true, messsage: '请输入用户名' }, { validator: this.checkUsername }]
})(<Input prefix={<Icon type="user" style={{ color: 'rgba(0,0,0,.25)' }} />} placeholder="用户名" />)
}
</Form.Item>
<Form.Item>
{
getFieldDecorator('password', {
rules: [{ required: true, messsage: '请输入密码' }]
})(<Input prefix={<Icon type="lock" style={{ color: 'rgba(0,0,0,.25)' }} />} placeholder="密码" />)
}
</Form.Item>
{
this.state.isSignUp && <Form.Item>
{
getFieldDecorator('email', {
rules: [{ type: 'email' }, { required: true, messsage: '请输入邮箱' }]
})(<Input prefix={<Icon type="mail" style={{ color: 'rgba(0,0,0,.25)' }} />} placeholder="邮箱" />)
}
</Form.Item>
}
<Form.Item>
<Button type="primary" htmlType="submit" className="login-form-button" >{this.state.isSignUp ? '注册' : '登录'}</Button>
<a onClick={() => this.setState({ isSignUp: !this.state.isSignUp })}>{this.state.isSignUp ? ' 已有账号?立即登录' : ' 没有账号?立即注册'}</a>
</Form.Item>
</Form>
)
}
}
const WrappedUserForm = Form.create()(UserForm);
4.2 admin组件 [#](#t184.2 admin组件)
.admin-page{
.admin-header{
height:50px;
line-height: 50px;
padding:0 50px;
background:skyblue;
}
}
import React, { Component } from 'react';
import Header from '../../components/Header';
export default class Admin extends Component {
render() {
return (
<div className="admin-page">
<Header />
</div>
)
}
}
4.3 header组件 [#](#t194.3 header组件)
import React, { Component } from 'react';
import { Row, Col, Icon } from 'antd';
import { Link, withRouter } from 'react-router-dom';
import user from '../../service/user';
class Header extends Component {
state = { username: '' }
signout = () => {
user.signout().then(() => this.props.history.push('/'))
}
componentWillMount() {
this.setState({ username: sessionStorage.getItem('username') });
}
render() {
return (
<Row className="admin-header">
<Col span="12">
<Link to="/admin">沐枫博客</Link>
</Col>
<Col span="12">
<div style={{ float: 'right' }}>
欢迎 {this.state.username}
<a style={{ marginLeft: 10 }} onClick={this.signout}><Icon type="logout" />退出</a>
</div>
</Col>
</Row>
)
}
}
export default withRouter(Header);
4.4 header组件 [#](#t204.4 header组件)
service\user.js
function signin(data) {
return post('/api/users/signin', data).then(res => {
sessionStorage.setItem('username', res.data.user.username);
return res;
});
}
function signout() {
return get('/api/users/signout').then(res => {
sessionStorage.removeItem('username');
return res;
});
}
\src\router.js
import createHistory from 'history/createHashHistory';
let history = createHistory();
history.listen((location) => {
console.log(location);
if (location.pathname != "/" && !sessionStorage.getItem('username')) {
history.push('/');
}
});
<Router history={history}>
5. 实现左侧的导航 [#](#t215. 实现左侧的导航)
5.1 NavLeft [#](#t225.1 NavLeft)
import React, { Component } from 'react';
import { Menu } from 'antd';
import { Link } from 'react-router-dom';
export default class NavLeft extends Component {
getMenus = (menus) => {
return menus.map(item => (
<Menu.Item key={item.key} title={item.title}>
<Link to={item.key}>{item.title}</Link>
</Menu.Item>
));
}
render() {
return (
<Menu
theme="light"
mode="inline"
defaultSelectedKeys={[window.location.hash.slice(1)]}
>
{
this.getMenus(this.props.menus)
}
</Menu>
)
}
}
5.2 admin [#](#t235.2 admin)
config\menus.js
export default [
{
title: '分类管理',
key: '/admin/category'
},
{
title: '文章管理',
key: '/admin/article'
}
]
+ import NavLeft from '../../components/NavLeft';
+ import menus from '../../config/menus';
<div className="admin-page">
<Header />
<Row className="welcome-page">
+ <Col span="3">
+ <NavLeft menus={menus} />
+ </Col>
<Col span="21">
</Col>
</Row>
</div>
6. 实现右侧的欢迎页和分类路由 [#](#t246. 实现右侧的欢迎页和分类路由)
6.1 admin\index.js [#](#t256.1 admin\index.js)
+ <Col span="21">
+ <Route exact path="/admin" component={Welcome} />
+ <Route exact path="/admin/category" component={Category} />
+ </Col>
6.2 Welcome\index.js [#](#t266.2 Welcome\index.js)
import React, { Component } from 'react';
export default class Welcome extends Component {
render() {
return (
<div className="admin-welcome">
<h2>欢迎登录沐枫博客</h2>
</div>
)
}
}
6.3 Category [#](#t276.3 Category)
import React, { Component } from 'react';
export default class Category extends Component {
render() {
return (
<div className="admin-welcome">
<h2>Category</h2>
</div>
)
}
}
7. 实现分类管理 [#](#t287. 实现分类管理)
7.1 实现分类组件 [#](#t297.1 实现分类组件)
import React, { Component } from 'react';
import { Row, Col, Table, Button, Modal, message, Popconfirm, Input, Form } from 'antd';
import service from '../../service/category';
export default class Category extends Component {
state = {
keyword: '',
items: [],//数据项
pagination: {},
loading: false,
editVisisble: false,
isCreate: true,
item: {},
selectedRowKeys: []
}
componentDidMount() {
this.getList({});
}
getList = () => {
this.setState({ loading: true }, () => {
service.list({ pageNum: this.state.pagination.current, keyword: this.state.keyword }).then(res => {
if (res.code == 0) {
const { items, pageNum: current, pageSize, total, } = res.data;
console.log(items);
this.setState({
items: items.map(item => (item.key = item._id, item)),
loading: false,
pagination: {
total,
pageSize,
current,
showTotal: () => `共${total}条`,
showQuickJumper: true,
onChange: this.pageChange
}
});
} else {
message.error(res.error);
}
});
});
}
create = () => {
this.setState({ editVisisble: true, isCreate: true });
}
onEditCancel = () => {
this.setState({ editVisisble: false });
}
onEditOk = () => {
let category = this.editform.props.form.getFieldsValue();
service[this.state.isCreate ? 'create' : 'update'](category).then(res => {
if (res.code == 0) {
this.setState({ editVisisble: false }, this.getList);
} else {
message.error(res.error);
}
});
}
handleSearch = (keyword) => {
this.setState({ keyword }, this.getList);
}
pageChange = (current) => {
this.setState({ pagination: { ...this.state.pagination, current } }, this.getList);
}
update = (item) => {
this.setState({ isCreate: false, item, editVisisble: true });
}
remove = (id) => {
service.remove(id).then(res => {
if (res.code == 0) {
this.setState({ pagination: { ...this.state.pagination, current: 1 } }, this.getList);
}
})
}
deleteSelected = () => {
service.remove(this.state.selectedRowKeys).then(res => {
if (res.code == 0) {
this.setState({ pagination: { ...this.state.pagination, current: 1 } }, this.getList);
}
})
}
render() {
const columns = [
{
title: '名称',
width: 500,
dataIndex: 'name',
key: 'name'
},
{
title: '操作',
dataIndex: 'action',
render: (text, record, index) => {
return (
<Button.Group>
<Button type="primary" onClick={() => this.update(record)}>修改</Button>
<Popconfirm onConfirm={() => this.remove(record._id)}>
<Button style={{ marginLeft: 10 }} type="danger">删除</Button>
</Popconfirm>
</Button.Group>
)
}
}
]
const rowSelection = {
selectedRowKeys: this.state.selectedRowKeys,
onChange: (selectedRowKeys) => {
this.setState({ selectedRowKeys });
}
}
return (
<div style={{ padding: 10 }}>
<Row>
<Col span="6">
<Button.Group>
<Button type="dashed" icon="save" onClick={this.create}>添加分类</Button>
<Button type="danger" onClick={this.deleteSelected}>删除所选分类</Button>
</Button.Group>
</Col>
<Col span="18">
<Input.Search
enterButton
placeholder="请输入关键字"
onSearch={keyword => this.handleSearch(keyword)}
/>
</Col>
</Row>
<Table
columns={columns}
dataSource={this.state.items}
bordered
loading={this.state.loading}
pagination={this.state.pagination}
rowSelection={rowSelection}
/>
<Modal
title={this.state.isCreate ? '添加分类' : '编辑分类'}
onCancel={this.onEditCancel}
onOk={this.onEditOk}
destroyOnClose
visible={this.state.editVisisble}
>
<WrappedEditModel
wrappedComponentRef={inst => this.editform = inst}
item={this.state.item}
isCreate={this.state.isCreate}
/>
</Modal>
</div>
)
}
}
class EditModel extends Component {
render() {
const { getFieldDecorator } = this.props.form;
return (
<Form>
<Form.Item>
{
getFieldDecorator('name', {
initialValue: this.props.isCreate ? '' : this.props.item.name
})(
<Input placeholder="请输入分类名称" />
)
}
</Form.Item>
<Form.Item>
{
getFieldDecorator('id', {
initialValue: this.props.isCreate ? '' : this.props.item._id
})(
<Input type="hidden" />
)
}
</Form.Item>
</Form>
)
}
}
const WrappedEditModel = Form.create()(EditModel);
7.2 实现分类接口 [#](#t307.2 实现分类接口)
import { get, post, put, del } from './index';
import qs from 'qs';
const entity = '/api/categories';
function list(query) {
return get(`${entity}?${qs.stringify(query)}`);
}
function create(data) {
return post(entity, data);
}
function update(data) {
return put(`${entity}/${data.id}`, data);
}
function remove(ids) {
if (typeof ids == 'string') {
ids = [ids];
}
return del(`${entity}/${ids[0]}`, { ids });
}
export default {
list,
create,
update,
remove
}
8. 实现文章管理 [#](#t318. 实现文章管理)
pages\article\index.js
import React, { Component } from 'react';
import { Row, Col, Table, Button, Input, Divider, Modal, Form, Popconfirm, Select, Card, List, Avatar, Spin } from 'antd';
import service from '../../service/article';
import category from '../../service/category';
export default class Article extends React.Component {
state = {
items: [],
selectedRows: [],
selectedRowKeys: [],
pageNum: 1,
keyword: '',
loading: false,
editVisible: false,
viewVisible: false,
commentVisible: false,
item: {},
categorires: []
}
componentDidMount() {
category.list({ pageNum: 1 })
.then(response => {
if (response.code == 0) {
let { items: categories } = response.data;
this.setState({ categories });
}
})
this.getList();
}
pagechange = (pageNum) => {
this.setState({ pageNum }, this.getList);
}
getList = () => {
this.setState({ loading: true }, () => {
service.list({ pageNum: this.state.pageNum, keyword: this.state.keyword })
.then(response => {
if (response.code == 0) {
let { items, total, pageNum: current, pageSize } = response.data;
items = items.map(item => (item.key = item._id, item));
this.setState({
items,
loading: false,
pagination: {
total,
pageSize,
current,
showTotal() {
return `共${total}条`;
},
showQuickJumper: true,
onChange: this.pagechange
}
});
}
})
});
}
onEditOk = () => {
let article = this.editForm.props.form.getFieldsValue();
service[this.state.isAdd ? 'create' : 'update'](article).then(response => {
if (response.code == 0) {
this.setState({ editVisible: false }, this.getList);
}
})
}
add = () => {
this.setState({ editVisible: true, isAdd: true });
}
onEditCancel = () => {
this.setState({ editVisible: false });
}
view = (item) => {
service.pv(item._id).then(response => {
if (response.code == 0) {
this.setState({
item,
viewVisible: true
}, this.getList);
}
})
}
onViewCancel = () => {
this.setState({
viewVisible: false
});
}
edit = (item) => {
this.setState({
isAdd: false,
item,
editVisible: true
});
}
remove = (id) => {
service.remove(id).then(response => {
if (response.code == 0) {
this.setState({ pageNum: 1 }, this.getList);
}
})
}
comment = (item) => {
this.setState({ commentVisible: true, item });
}
CommentCancel = () => {
this.setState({ commentVisible: false });
}
onCommentOk = () => {
let data = this.commentForm.props.form.getFieldsValue();
service.comment(this.state.item._id, data).then(response => {
if (response.code == 0) {
this.setState({ commentVisible: false }, this.getList);
}
})
}
handleDeleteComment = (article_id, comment_id) => {
service.removeComment(article_id, comment_id).then(response => {
if (response.code == 0) {
this.setState({ commentVisible: false }, this.getList);
}
})
}
render() {
const { selectedRowKeys, loading } = this.state;
const rowSelection = {
selectedRowKeys,
onChange: (selectedRowKeys, selectedRows) => {
this.setState({ selectedRowKeys, selectedRows });
}
}
const columns = [
{
title: '标题',
dataIndex: 'title',
width: 100
},
{
title: '分类',
dataIndex: 'category',
render: text => text.name,
width: 100
},
{
title: '内容',
dataIndex: 'content',
width: 100
},
{
title: '浏览量',
dataIndex: 'pv',
width: 100
},
{
title: '评论量',
dataIndex: 'text',
render: (text, record) => {
return record.comments.length;
},
width: 100
},
{
title: '作者',
dataIndex: 'user',
render: (text, record) => {
console.log(record);
return text.username;
},
width: 100
},
{
title: '发表时间',
dataIndex: 'createAt',
render: (text, record) => {
return text.toLocaleString();
},
width: 100
},
{
title: '操作',
key: 'action',
render: (text, record) => (
<Button.Group>
<Button
type="dash"
icon="edit"
onClick={() => this.view(record)}
>
查看
</Button>
<Button
type="primary"
style={{ marginLeft: 5 }}
icon="edit"
onClick={() => this.comment(record)}
>
评论
</Button>
<Button
style={{ marginLeft: 5 }}
type="primary"
icon="eye"
onClick={() => this.edit(record)}
>
编辑
</Button>
<Popconfirm title="确认删除吗?" onConfirm={() => this.remove(record._id)}>
<Button
style={{ marginLeft: 5 }}
type="danger"
icon="delete"
>删除</Button>
</Popconfirm>
</Button.Group>
)
}
]
return (
<Row style={{ margin: '3px' }}>
<Col span="24">
<Row>
<Col span="18">
<Button.Group>
<Button type="dashed" icon="save" onClick={this.add}>创建文章</Button>
<Button
type="danger"
icon="delete"
style={{ marginleft: 5 }}
onClick={() => this.remove(this.state.selectedRowKeys)}
>全部删除</Button>
</Button.Group>
</Col>
<Col span="6">
<Input.Search
onSearch={keyword => this.setState({ pageNum: 1, keyword }, this.getList)}
enterButton={true}>
</Input.Search>
</Col>
</Row>
<Table
loading={loading}
style={{ margin: 3 }}
bordered
columns={columns}
dataSource={this.state.items}
pagination={this.state.pagination}
rowSelection={rowSelection}
>
</Table>
<Modal
title={this.state.title}
visible={this.state.editVisible}
onOk={this.onEditOk}
onCancel={this.onEditCancel}
width={800}
closable
destroyOnClose
>
<WrappedEditModal
wrappedComponentRef={instance => this.editForm = instance}
isAdd={this.state.isAdd}
categories={this.state.categories}
item={this.state.item}
/>
</Modal>
<Modal
title={this.state.title}
visible={this.state.viewVisible}
onCancel={this.onViewCancel}
width={800}
closable
footer={null}
maskClosable
destroyOnClose
>
<WrappedViewModal
item={this.state.item}
/>
</Modal>
<Modal
visible={this.state.commentVisible}
onOk={this.onCommentOk}
onCancel={this.CommentCancel}
width={800}
closable
destroyOnClose
>
<WrappedCommentModal
wrappedComponentRef={instance => this.commentForm = instance}
item={this.state.item}
handleDeleteComment={this.handleDeleteComment}
/>
</Modal>
</Col>
</Row>
)
}
}
class EditModal extends Component {
render() {
let item = this.props.item;
const { getFieldDecorator } = this.props.form;
return (
<Form>
<Form.Item label="分类">
{
getFieldDecorator('category', { initialValue: this.props.isAdd ? (this.props.categories.length > 0 ? this.props.categories[0]._id : '') : this.props.item.category._id })(
<Select>
{
this.props.categories.map(category => (
<Select.Option key={category._id} value={category._id}>
{category.name}
</Select.Option>
))
}
</Select>
)
}
</Form.Item>
<Form.Item label="标题">
{
getFieldDecorator('title', { initialValue: this.props.isAdd ? '' : this.props.item.title, rules: [{ required: true, message: '请输入标题' }] })(
<Input maxLength={50} placeholder="请输入标题" />
)
}
</Form.Item>
<Form.Item label="内容">
{
getFieldDecorator('content', { initialValue: this.props.isAdd ? '' : this.props.item.content, rules: [{ required: true, message: '请输入内容' }] })(
<Input.TextArea placeholder="请输入内容" />
)
}
</Form.Item>
{
!this.props.isAdd && getFieldDecorator('id', { initialValue: this.props.item._id })(
<Input type="hidden" />
)
}
</Form>
)
}
}
class ViewModal extends Component {
render() {
return (
<Card title={this.props.item.title} style={{ marginTop: 20 }}>
<p>分类:{this.props.item.category.name}</p>
<p>内容:{this.props.item.content}</p>
</Card>
)
}
}
class CommentModal extends Component {
state = {
loading: false,
skip: 0,
limit: 5,
data: this.props.item.comments.slice(0, 5)
}
handleLoadMore = () => {
this.setState({
skip: this.state.skip += this.state.limit,
data: this.props.item.comments.slice(0, this.state.skip + this.state.limit)
});
}
render() {
const { skip, limit, loading, data } = this.state;
const { item, form: { getFieldDecorator } } = this.props;
const loadMore = (
skip + limit < item.comments.length ? (
<div style={{ marginTop: 10, textAlign: 'center' }}>
{loading && <Spin />}
{!loading && <Button onClick={this.handleLoadMore}>加载更多</Button>}
</div>) : null
)
return (
<Row style={{ marginTop: 20 }}>
<Col span="24">
<Form>
<Form.Item>
{
getFieldDecorator('content', { rules: [{ required: true, message: '请输入评论内容' }] })(
<Input placeholder="请输入评论内容" />
)
}
</Form.Item>
</Form>
<List
loading={this.state.loading}
loadMore={loadMore}
dataSource={data}
renderItem={
item => (
<List.Item actions={[<Button type="danger" onClick={() => this.props.handleDeleteComment(this.props.item._id, item._id)} icon="delete">删除</Button>]} >
<List.Item.Meta
avatar={<Avatar src="https://zos.alipayobjects.com/rmsportal/ODTLcjxAfvqbxHnVXCYX.png" />}
title={item.user.username}
description={item.user.email}
/>
<div>{item.content}</div>
</List.Item>
)
}
/>
</Col>
</Row>
)
}
}
const WrappedEditModal = Form.create()(EditModal);
const WrappedViewModal = Form.create()(ViewModal);
const WrappedCommentModal = Form.create()(CommentModal);
\src\service\article.js
import { get, post, put, del } from './index';
import qs from 'qs';
const entity = '/api/articles';
function list({ pageNum = 1, pageSize = 5, keyword = "" }) {
return get(`${entity}?pageNum=${pageNum}&pageSize=${pageSize}&keyword=${keyword}`);
}
function create(data) {
return post(entity, data);
}
function update(data) {
return put(`${entity}/${data.id}`, data);
}
function remove(ids) {
if (typeof ids == 'string') {
ids = [ids];
}
return del(`${entity}/${ids[0]}`, { ids });
}
function comment(id, data) {
return post(`${entity}/comment/${id}`, data);
}
function removeComment(article_id, comment_id) {
return del(`${entity}/${article_id}/comment/${comment_id}`);
}
function pv(id) {
return get(`${entity}/pv/${id}`);
}
export default {
list,
create,
update,
remove,
comment,
removeComment,
pv
}
Gitalking ...
Be the first guy leaving a comment!