2018-09-29

配置项目


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
}

9. 支持markdown [#](#t329. 支持markdown)

Gitalking ...

Markdown is supported

Be the first guy leaving a comment!