用React和Ant Design 搭建一个简单的后台管理系统

背景

React是一个作为一个非常流行的前端框架,其生态圈及其繁荣,社区里有很多基于它的库。Ant Design就是其中一个非常好用的ui库。
这是蚂蚁金服的一个ui库,里面封装了很多很好用的组件。

现在搭建的后台管理系统就是利用Ant Design的组件来快速开发。

准备工作

首先最基本的肯定是要配置好React 开发所需要的环境。

这里,最快的方法是通过create-react-app脚手架来快速构建项目了。

打开命令行,全局安装create-react-app

npm install create-react-app -g

然后进入要存放项目的目录,执行命令,新建我们的项目

create-react-app react_ant

最后,进入我们的项目,执行

npm start 

浏览器就会自动打开,并跳转到http://localhost:3000/,这就表明,我们的开发环境已经建好。可以随便更改app.jsrender中的渲染内容来检验。

然后,我们的准备工作继续。

到现在,我们的开发环境是搭建起来了,但是我们的目标是是要搭建后台的管理系统,这其中,对用户数据的查改增删操作都要涉及到和后端的交互,我们总不能还要另外去租个服务器吧?其实,对于我们现在要处理的简单数据,完全没必要,那用什么能实现这些简单的需求来模拟我们线上的请求呢?json-server就能满足我们。

json-server可以为前端开发者快速搭建一个后端服务器。我们可以发送get,post, put, delete请求来修改服务器上预先设定好的假数据。

使用方法如下:

  1. 安装
npm install json-server -g
  1. 进入我们的 react-ant项目,在项目根目录里面创建给文件夹叫做:server .在里面创建一个db.json。 在db.json先整一些数据,数据最外层得是一个对象,对象里的数组里的每条对象最好有id
  1. 进入server文件夹,执行命令,启动server

    json-server 数据文件名 -w -p 端口号
    

这时候,我们就终端窗口就会提示我们的server启动信息

img

这时候我们直接访问localhost:2000/db就能看到我们的数据。

Markdown

  1. 然后在自己的项目里即可发起get/post/put/delete 请求
服务器搭建好后,我们还要数据。

可以使用faker快速生成前端开发需要的假数据。

中文版 faker-zh-cn

使用步骤:

  1. 安装

    npm install faker-zh-cn --save--dev
    
  1. 进入server文件夹,新建people.js文件

根据需要的数据,结合npm上的api,编写people.js

var faker = require('faker-zh-cn');

function getData() {
    var arr = [];

    for (let i = 1; i <= 30; i++) {
        arr.push({
            id: i,
            name: faker.Name.findName(),
            email: faker.Internet.email(),
            city: faker.Address.city()
        })
    }


    return {peopleList: arr};
}

module.exports = getData;

然后正常打开json-server,在浏览器打开对应网址,即能看到数据。

Markdown

开始编码

项目结构

要做的这个后台管理系统很简单,只包含三个页面,列表页,编辑页,添加用户数据,同时还包含一个每个页面都会有的侧边栏,点击侧边栏可以切换列表页和添加用户数据页。列表页的每条数据都要设置删除和编辑用户数据的方法,并且每次操作数据都提供对用户体验良好的提示等。

项目的目录如下:

Markdown

  • public 存放的是公共的东西,包括Logo,单页面的html文件。
  • server 存放后端数据的文件
  • src 存放各个页面组件。

接下来说一下每个文件的具体作用。

index.js

作为一个单页面的项目,要想实现页面的切换,路由就必不可少。index.js主要是定义页面的路由,同时,也引入了Ant Design的样式文件,它会提供一些很不错的默认样式。

import React from 'react';
import ReactDOM from 'react-dom';
import {Router, Route, browserHistory, IndexRoute } from 'react-router';
import App from './app';
import List from './list';
import Add from './add';
import Edit from './edit';
import 'antd/dist/antd.css';

ReactDOM.render(
  <Router history={ browserHistory }>
    <Route path='/' component={ App }>
      <IndexRoute component={ List }></IndexRoute>
      <Route path="list" component={ List }></Route>
      <Route path="add" component={ Add }></Route>
      <Route path='edit/:id' component={ Edit }></Route>      
    </Route>
  </Router> 
,    
  document.getElementById('root')
);
app.js

定义了路由,我们就可以开始页面的基本布局,即父组件。我从Ant Design 的文档中选择了左边是侧边栏,右边是内容显示区的这么一个简单布局方案,并且局部膝盖了它的默认样式,如修改了<Content />的的minHeight,让它不管内容多少都基本占满整个屏幕。

同时给侧边栏的选项添加了点击事件,点击时改变样式,并且切换页面。

import React from 'react';
import { Link } from 'react-router';
import { Layout, Menu, Icon } from 'antd';
const { Header, Sider, Content } = Layout;


class App extends React.Component {

    constructor(props) {
        super(props);
    }

  state = {
    collapsed: false,
  };

  toggle = () => {
    this.setState({
      collapsed: !this.state.collapsed,
    });
  }

  select = (item) => {  // 点击侧边栏中的各个选项切换页面
    //   console.log(item.key);
      let path = item.key === '1' ? '/' : 'add';
      this.props.history.push(path);
  }


  render() {
    return (
      <Layout>
        <Sider
          trigger={null}
          collapsible
          collapsed={this.state.collapsed}
        >
          <div className="logo" style={{height: 40}} />
          <Menu theme="dark" mode="inline" defaultSelectedKeys={['1']} onClick={ this.select }>
            <Menu.Item key="1" >
              <Icon type="bars" />
              <span className="nav-text">列表</span>
            </Menu.Item>
            <Menu.Item key="2" >
              <Icon type="user-add" />
              <span className="nav-text">添加用户</span>
            </Menu.Item>
          </Menu>
        </Sider>
        <Layout>
          <Header style={{ background: '#fff', padding: 0 }}>
            <Icon
              className="trigger"
              type={this.state.collapsed ? 'menu-unfold' : 'menu-fold'}
              onClick={this.toggle}
              style={{fontSize: 20, marginLeft: 10, cursor: 'pointer'}}
            />
          </Header>
          <Content style={{ margin: '24px 16px', padding: 24, background: '#fff', minHeight: 710 }}>
            { this.props.children }
          </Content>
        </Layout>
      </Layout>
    );
  }
}



export default App;
list.js

列表子组件,放在app.js<Content />中。

首先自定义<App />组件,在componentWillMount时,向json-serverGET请求之前生成的假数据。

拿到数据之后,通过Ant Design中的<Table />组件,稍加配置,就能将列表渲染出来。

之后就是在每条数据末尾增加删除和编辑的按钮。给删除按钮绑定了点击事件,一旦点击就发起delete请求,删除服务器中的数据。而点击编辑按钮,即条状到编辑页面来编辑这条信息。

import React from 'react';
import axios from 'axios';
import { Link } from 'react-router';
import { Table, Input, Popconfirm,Button } from 'antd';


class List extends React.Component {
  constructor(props) {
    super(props);
    this.columns = [{
        title: 'id',
        dataIndex: 'id'
    }, {
      title: 'name',
      dataIndex: 'name',
      width: '20%'
    }, {
      title: 'email',
      dataIndex: 'email',
    }, {
      title: 'city',
      dataIndex: 'city',
       width: '20%'
    }, {
      title: 'operation',
      dataIndex: 'operation',
      render: (text, record, index) => {

        return (
          <div className="editable-row-operations">
            <Button type='danger' size='small' style={ {marginRight: 10} } ghost  onClick={ this.delete.bind(this, text, record, index) }>删除</Button>
            <Button type='primary' size='small' ghost  onClick={ this.edit.bind(this, text, record, index) }>编辑</Button>          
          </div>
        );
      },
    }];

    this.state = {
      list: []

    };
  }

    componentWillMount() {
    // 请求数据
    axios.get('http://localhost:2000/peopleList')
        .then(
            res => {
                // console.log(res);
                this.setState({
                    list: res.data
                })
            },
            err => console.log(err)
        )
    } 

  render() {
    const { list } = this.state;
    const columns = this.columns;
    return (
      <div>
        <Table bordered dataSource={list} columns={columns} />
      </div>
    );
  }

  delete = ( text, record, index) => {
    // text 为undefined
    // index为分页列表的下标,因为antd-desigin的分页之后下标重新开始, 所以删除的永远是第一个分页的数据,不能用
    // record为点击的那条数据对象
    // console.log(record)
    // console.log(text)

    // 寻找该条数据的实际下标
    let itemIndex = 0;
    for (let i = 0; i < this.state.list.length; i++) {
      if (this.state.list[i].id === record.id) {
        itemIndex = i;
      }
    }

    if (confirm(`确认将${ record.name }移除吗?`)) {
        axios.delete("http://localhost:2000/peopleList/" + record.id)
            .then(
                res => {
                    console.log(res.data); // 删除成功的话返回一个空数组
                    if (res.data) {
                        this.state.list.splice(itemIndex, 1);
                        this.setState({
                            list: this.state.list
                        })
                    }
                },
                err => console.log(new Error(err))
            )            
    }  
  }

  edit = ( text, record, index) => {
     // 寻找该条数据的实际下标
      let itemIndex = 0;
      for (let i = 0; i < this.state.list.length; i++) {
        if (this.state.list[i].id === record.id) {
          itemIndex = i;
        }
      }

      this.props.history.push('edit/' + this.state.list[itemIndex].id);
  }



}



export default List;

效果如下图:

Markdown

edit.js

点击编辑按钮之后,页面会跳转到编辑页,该页面用到了Ant Design封装的表单组件,通过react-router传递过来的数据,即可在组件render前请求数据,之后将原来的用户信息渲染的页面。

然后编辑之后,对`json-server`发起`PUT`请求,在请求URL中加上id值,即可修改对应用户的信息。

import React from 'react';
import axios from 'axios';

import { Form, Icon, Input, Button, Checkbox } from 'antd';
const FormItem = Form.Item;

class Edit extends React.Component {
      constructor(props) {
        super(props);
        this.state = {
            item: {}
        }
    }

    // 请求该用户的原始数据
    componentWillMount() {
        axios.get('http://localhost:2000/peopleList/' + this.props.params.id)
            .then(
                res => {
                    // console.log(res.data);
                    this.setState({
                        item: res.data
                    });

                },
                err => console.log(new Error(err))
            )
    }

  render() {

    return (
      <Form onSubmit={this.handleSubmit} className="login-form">
        <FormItem>
            姓名:
            <Input prefix={<Icon type="user" style={{ fontSize: 13 }} />} placeholder="姓名" value={this.state.item.name} onChange={ (e) => { this.change(e, 'name') } }/>
        </FormItem>
        <FormItem>
            邮箱:
            <Input prefix={<Icon type="contacts" style={{ fontSize: 13 }} />} type="text" placeholder="请输入邮箱" value={ this.state.item.email } onChange={ (e) => { this.change(e, 'email') } }/>
        </FormItem>
        <FormItem>
            城市:
            <Input prefix={<Icon type="compass" style={{ fontSize: 13 }} />} type="text" placeholder="城市" value={ this.state.item.city } onChange={ (e) => { this.change(e, 'city') } }/>
        </FormItem>
        <FormItem>
          <Button type="primary" htmlType="submit" className="login-form-button" onClick={ this.submit }>
            确认修改
          </Button>
        </FormItem>
      </Form>
    );
  }


     // 改变输入框内容
    change = (e, key) => {
        this.state.item[key] = e.target.value;
        this.setState({
            item: this.state.item
        })
    }

    submit = () => {
        // 请求接口修改数据
        axios.put('http://localhost:2000/peopleList/' + this.props.params.id, {
           ...this.state.item
        })
             .then(
                res =>{
                    console.log(res.data);
                    if (res.data) {
                        alert('修改成功!');
                        this.props.history.push('/');
                    }
                },
                err => console.log(err)
             )
    }


}

export default Edit;

效果图:

Markdown

add.js

这个组件和编辑页面类似,点击侧边栏的增加用户选项即可进入。

只不过进来的时候表单的数据都为空。

Ant Design在<Input />组件中有个让人不爽的地方,在这个组件用ref能拿到组件的一些属性方法,但是取不到input框的value,所以这里需要绕一点弯路。

点击保存,会向json-server发送POST请求,添加数据。

还有个需要注意的点: 添加的数据不需要指定Id,json-server会为我们自动添加。

代码逻辑和效果图与edit.js类似。

以上,我们的项目基本完成。

小结

通过这个小项目,我们可以学到以下几点知识:

  1. 通过create-react-app脚手架快速配置React的开发环境。
  2. 通过json-server能让我们前端开发人员模拟完成get/post/put/delete请求。
  3. 通过faker快速生成json数据,用于开发,测试。
  4. 通过Ant Design中的组件快速开发我们的项目。