react+react-router+redux开发体育馆管理系统(3)--场地类型管理模块开发

接下来实现的是场地类型管理模块,先把主页的框架写出来,ui库使用的是antd
附官网地址:https://ant.design/index-cn

主页框架实现

在container目录下新建index.js(原来那个Main.js不要了)
index.js

require('normalize.css/normalize.css');
require('styles/App.css');

import React from 'react';

//路由模块
import {Link} from 'react-router-dom'

//引入antd模块
import {Layout, Menu, Icon} from 'antd'
const SubMenu = Menu.SubMenu;
const {Header, Content, Sider} = Layout;

import 'antd/dist/antd.css'


let yeomanImage = require('../images/yeoman.png');

class AppComponent extends React.Component {
  state = {
    current: '',
    username: 'shilim',
    collapsed: false,
    mode:'inline'
  }
  toggle = () => {
    this.setState({
      collapsed: !this.state.collapsed,
      mode: this.state.collapsed ? 'inline' : 'vertical'
    })
  }
  handleClick = (e) => {
    this.setState({
      current: e.key
    });
  }

  render() {
    return (
      <Layout id="main-view">
        <Sider
          trigger={null}
          collapsible
          collapsed={this.state.collapsed}
        >
          <img src={yeomanImage} width="50" id="logo"/>
          <Menu theme="dark"
                onClick={this.handleClick}
                defaultSelectedKeys={[this.state.current]}
                mode={this.state.mode}
          >
            <SubMenu key="sub1" title={<span><Icon type="user"/><span>用户管理</span></span>}>
              <Menu.Item key="1"><Link to="/userList">用户管理</Link></Menu.Item>
              <Menu.Item key="2"><Link to="/roleList">角色管理</Link></Menu.Item>
            </SubMenu>
            <SubMenu key="sub2" title={<span><Icon type="tool"/><span>器材管理</span></span>}>
              <Menu.Item key="4"><Link to="/equipmentTypeList">类型管理</Link></Menu.Item>
              <Menu.Item key="5"><Link to="/equipmentList">器材管理</Link></Menu.Item>
              <Menu.Item key="6"><Link to="/equipmentLoanList">租借管理</Link></Menu.Item>
            </SubMenu>
            <SubMenu key="sub3" title={<span><Icon type="appstore-o"/><span>场地管理</span></span>}>
              <Menu.Item key="7"><Link to="/placeTypeList">类型管理</Link></Menu.Item>
              <Menu.Item key="8"><Link to="/placeList">场地管理</Link></Menu.Item>
              <Menu.Item key="9"><Link to="/placeLeaseRecordList">租借管理</Link></Menu.Item>
            </SubMenu>
            <SubMenu key="sub4" title={<span><Icon type="schedule"/><span>赛事管理</span></span>}>
              <Menu.Item key="10"><Link to="/gameList">赛事管理</Link></Menu.Item>
              <Menu.Item key="11"><Link to="/noticeList">公告管理</Link></Menu.Item>
            </SubMenu>
          </Menu>
        </Sider>
        <Layout>
          <Header style={{background: '#fff', padding: 0,borderBottom:'1px solid #e9e9e9'}}>
            <Icon
              className="trigger"
              type={this.state.collapsed ? 'menu-unfold' : 'menu-fold'}
              onClick={this.toggle}
            />
            <span>体育馆管理系统</span>
          </Header>
          <Content>
              { this.props.children }
          </Content>
        </Layout>
      </Layout>
    );
  }
}

AppComponent.defaultProps = {};

export default AppComponent;

App.css

/* Base Application Styles */
html,body,#app{
  width: 100%;
  height: 100%;
}
#main-view{
  height: 100%;
}
.trigger {
  font-size: 18px;
  line-height: 64px;
  padding: 0 16px;
  cursor: pointer;
  transition: color .3s;
}

.trigger:hover {
  color: #108ee9;
}

#logo {
  display: block;
  margin: 20px auto;
}

.ant-layout-sider-collapsed .anticon {
  font-size: 16px;
}

.ant-layout-sider-collapsed .nav-text {
  display: none;
}

路由配置

如果没有安装react-router通过npm install react-router -save安装
修改routers.js

import React from 'react';
import {HashRouter as Router, Route} from 'react-router-dom';
import createBrowserHistory from 'history/createBrowserHistory';

const history = createBrowserHistory()

import App from '../containers/index';

export default class RouterMap extends React.Component {
  render() {
    return (
      <Router history={history}>
        <App>
        </App>
      </Router>)
  }
}

启动来看一下,菜单已经出来了:
这里写图片描述

场地类型列表页实现

场地列表页使用了redux,其实这个项目并不需要用到redux。因为redux的状态应该是多个组件共享的状态,主要是为了解决组件间通讯的困难。这个项目中其实每个组件自己维护自己的状态就已经足够了,这里使用redux只是为了练习其用法。

1. reducer

先完成reducer
placeTypeManage.js

import * as actionTypes from '../constants/placeTypeManage';
import PageVo from '../models/PageVo';

const initialState = {
  page:new PageVo(1,5)
}
export function placeTypeManage(state=initialState,action) {
  switch (action.type) {
    case actionTypes.CHANGE_PAGE_NUM:
      return {
        ...state,
        page:action.page
      }
    case actionTypes.SAVE_PLACE_TYPE_LIST:
      return {
        ...state,
        placeTypeList:action.data
      }
    default:
      return state;
  }
}

修改reducers目录下的index.js,用于整合不同的reducer,便于分模块

import { combineReducers } from 'redux'

import { placeTypeManage } from './placeTypeManage'

export default combineReducers({
  placeTypeManage
})
2. action

view想要改变state,只能通过出发action,action通过dispatch来通过reducer改变state。修改action下面的
placeTypeManage.js

import * as actionTypes from '../constants/placeTypeManage';
import * as api from '../api/placeTypeManageApi';
const qs = require('qs');

import {notificationShow} from '../utils/notification'

export function changePageNum(page) {
  return {
    type: actionTypes.CHANGE_PAGE_NUM,
    page: page
  }
}

export function getPlaceTypeList(page) {
  const pageVo = qs.stringify(page);
  return dispatch => api.getPlaceTypeList(pageVo)
    .then((res) => {
      switch (res.data.serviceResult) {
        case 1:
          if(res.data.resultParam.list.length===0 && res.data.resultParam.pageNum>0) {
            let tempPage = JSON.parse(page.page);
            tempPage.pageNum--;
            dispatch(getPlaceTypeList({page:JSON.stringify(tempPage)}));
          } else {
            dispatch(savePlaceTypeList(res.data.resultParam))
          }
      }
    })
    .catch((error) => {
      notificationShow('error',error)
    })
}

export function savePlaceTypeList(data) {
  return {
    type: actionTypes.SAVE_PLACE_TYPE_LIST,
    data: data
  }
}

这里使用了qs,主要用于格式化发送的参数,axios使用post请求时,参数会被加上一个双引号,后台拿到的数据都会被加上双引号。所以使用qs使其变正常,不然后台无法接收到正常的参数。qs可以通过npm install qs --save下载

3. store

主要用于初始化state,修改stores下面的index.js

import {createStore,applyMiddleware} from 'redux';
import reducers from '../reducers/index';
import thunk from 'redux-thunk';

export default function configureStore() {
  let store = createStore(reducers,applyMiddleware(thunk),
    // 触发 redux-devtools
    window.devToolsExtension ? window.devToolsExtension() : undefined
  );
  return store;
}

这里使用了一个中间件thunk,主要是action能够进行异步操作,具体解释可以自行查资料

4. 页面实现

修改containers目录下的placeType目录下的placeTypeList.js

/**
 * Created by shilim on 2017/7/11.
 */
import React from 'react';
import {connect} from 'react-redux';
import {Link} from 'react-router-dom';

import {Breadcrumb, Button, Table, Icon, Pagination,Modal,Input} from 'antd'
const confirm = Modal.confirm;
const Search = Input.Search;
import {changePageNum, getPlaceTypeList} from '../../actions/placeTypeManage'
import {deletePlaceType} from '../../api/placeTypeManageApi'
import {notificationShow} from '../../utils/notification'
import PageVo from '../../models/PageVo'
import PlaceTypeVo from '../../models/PlaceTypeVo'
import QueueAnim from 'rc-queue-anim'

const qs = require('qs')

class PlaceTypePage extends React.Component {
  state = {
    selectedRowKeys: []
  }

  onSelectChange = (selectedRowKeys) => {
    this.setState({selectedRowKeys});
  }

  onSearch = (value) => {
    this.props.changePageNum(new PageVo(this.props.page.pageNum,
      this.props.page.pageSize,null,value,true))
    setTimeout(()=> {
      this.props.getPlaceTypeList({page: this.props.page.voToJson()})
    })
  }

  onPageChange = (pageNum, pageSize) => {
    this.props.changePageNum(new PageVo(pageNum, pageSize))
    setTimeout(()=> {
      this.props.getPlaceTypeList({page: this.props.page.voToJson()})
    })
  }

  onDeleteOne = (id) => {
    confirm({
      title:'您确认删除该记录吗?',
      onOk:() => {
        let placeTypeVo = new PlaceTypeVo();
        placeTypeVo.id = id;
        let placeTypeList = [];
        placeTypeList.push(placeTypeVo);
        deletePlaceType(qs.stringify({placeTypeList:JSON.stringify(placeTypeList)}))
          .then((res) => {
            switch (res.data.serviceResult) {
              case 1:
                notificationShow('success',res.data.resultInfo)
                this.props.getPlaceTypeList({page: this.props.page.voToJson()})
                break;
              default:
                notificationShow('error', res.data.resultInfo)
                break;
            }
          })
          .catch((err) => {
            notificationShow('error',err)
          })
      }
    })
  }

  render() {
    const rowSelection = {
      selectedRowKeys: this.state.selectedRowKeys,
      onChange: this.onSelectChange
    }
    const columns = [{
      title: '场地名称',
      dataIndex: 'placeTypeName'
    }, {
      title: '操作',
      width:150,
      render: (item) => (
        <span>
          <Link to={'/editPlaceType/'+item.id}><Icon type="edit"/></Link>
          <Icon type="delete" style={{cursor: 'pointer', marginLeft: 20,color:'red'}}
                onClick={() => this.onDeleteOne(item.id)}/>
        </span>
      )
    }];

    return (
      <QueueAnim duration="1200">
        <div key="placeTypeList" style={{padding: 15, background: '#fff', minHeight: 360}}>
          <Breadcrumb>
            <Breadcrumb.Item>场地管理</Breadcrumb.Item>
            <Breadcrumb.Item>场地类型列表</Breadcrumb.Item>
          </Breadcrumb>
          <div style={{margin: '15px 0'}}>
            <Link to="/addPlaceType"><Button type="primary" icon="plus">新增场地类型</Button></Link>
            <Search
              placeholder="输入类型名称查找"
              style={{ width: 200 ,float:'right'}}
              onSearch={this.onSearch}
            />
          </div>
          <div>
            <Table rowKey="id" rowSelection={rowSelection} columns={columns} pagination={false}
                   dataSource={this.props.placeTypeList ? this.props.placeTypeList.list : null}/>
            {
              this.props.placeTypeList ? (<Pagination style={{marginTop: 15}}
                                                          showTotal={(total) => `共${total}条数据`}
                                                          total={this.props.placeTypeList.total}
                                                          pageSize={this.props.placeTypeList.pageSize}
                                                          defaultCurrent={this.props.placeTypeList.pageNum}
                                                          current={this.props.placeTypeList.pageNum}
                                                          showQuickJumper={true}
                                                          onChange={this.onPageChange}></Pagination>) : (<div>加载中...</div>)
            }

          </div>
        </div>
      </QueueAnim>)
  }

  componentDidMount() {
    this.props.getPlaceTypeList({page: this.props.page.voToJson()})
  }
}

function mapStateToProps(state) {
  return {
    page: state.placeTypeManage.page,
    placeTypeList: state.placeTypeManage.placeTypeList
  }
}

function mapDispatchToProps(dispatch) {
  return {
    changePageNum: (page) => {
      dispatch(changePageNum(page))
    },
    getPlaceTypeList: (page) => {
      dispatch(getPlaceTypeList(page))
    }
  }
}

PlaceTypePage = connect(
  mapStateToProps,
  mapDispatchToProps
)(PlaceTypePage)

export default PlaceTypePage

connect就是把react和redux连接起来的关键,这个函数允许我们将 store 中的数据和action里的方法作为 props 绑定到组件上。这样我就可以展示store里面的数据,也能够通过调用action来修改state。

场地类型新增页实现

场地类型增加就不使用redux了,因为状态自己管理就已经足够了
修改containers目录下的placeType目录下的placeTypeAddition.js

import React from 'react';
import QueueAnim from 'rc-queue-anim'
import {Breadcrumb, Button, Popconfirm, Form, Input} from 'antd';
const FormItem = Form.Item;

import PlaceTypeVo from '../../models/PlaceTypeVo';
import {addPlaceType} from '../../api/placeTypeManageApi';
import {notificationShow} from '../../utils/notification';

const qs = require('qs');

class PlaceTypeAddictionPage extends React.Component {

  goBack = () => {
    this.props.history.push('/placeTypeList')
  }

  handleSubmit = (e) => {
    e.preventDefault();
    let placeTypeVo = new PlaceTypeVo();
    this.props.form.validateFields((err, values) => {
      if (!err) {
        placeTypeVo = Object.assign(placeTypeVo, values)
        addPlaceType(qs.stringify({placeType: placeTypeVo.voToJson()}))
          .then((res) => {
            switch (res.data.serviceResult) {
              case 1:
                notificationShow('success', res.data.resultInfo);
                this.goBack();
                break;
              default:
                notificationShow('error', res.data.resultInfo);
                break;
            }
          })
          .catch((error) => {
            notificationShow('error',error)
          })
      }
    });
  }

  render() {
    const {getFieldDecorator} = this.props.form;
    const formItemLayout = {
      labelCol: {
        xs: {span: 24},
        sm: {span: 6}
      },
      wrapperCol: {
        xs: {span: 24},
        sm: {span: 14}
      }
    };
    return (
      <QueueAnim duration="1200">
        <div key="placeTypeAddiction" style={{padding: 15, background: '#fff'}}>
          <Breadcrumb>
            <Breadcrumb.Item>场地管理</Breadcrumb.Item>
            <Breadcrumb.Item>新增场地类型</Breadcrumb.Item>
          </Breadcrumb>
          <div style={{margin: '15px 0'}}>
            <Popconfirm placement="top" title="您确定放弃当前添加记录吗?" okText="是" cancelText="否"
                        onConfirm={this.goBack}>
              <Button type="default" icon="rollback">返回</Button>
            </Popconfirm>
            <Button type="primary" icon="file" style={{marginLeft: 10}} onClick={this.handleSubmit}>保存</Button>
          </div>
          <div style={{borderTop: '1px dashed #e7eaec', margin: '20px 0'}}/>
          <Form>
            <FormItem {...formItemLayout} label="类型名称" hasFeedback>
              {getFieldDecorator('placeTypeName', {rules: [{required: true, message: '类型名称不能为空'}]})(
                <Input/>
              )}
            </FormItem>
          </Form>
        </div>
      </QueueAnim>
    )
  }

  componentDidMount() {
  }

}

PlaceTypeAddictionPage = Form.create()(PlaceTypeAddictionPage);

export default PlaceTypeAddictionPage

场地类型修改页实现

修改containers目录下的placeType目录下的placeTypeEdition.js

import React from 'react';
import QueueAnim from 'rc-queue-anim'
import {Breadcrumb, Button, Popconfirm, Form, Input} from 'antd';
const FormItem = Form.Item;

import PlaceTypeVo from '../../models/PlaceTypeVo';
import {getOnePlaceType,updatePlaceType} from '../../api/placeTypeManageApi';
import {notificationShow} from '../../utils/notification';

const qs = require('qs');

class PlaceTypeEditionPage extends React.Component {
  constructor(props) {
    super(props)
    this.state = {placeType:{}}
  }

  goBack = () => {
    this.props.history.push('/placeTypeList')
  }

  handleSubmit = (e) => {
    e.preventDefault();
    let placeTypeVo = new PlaceTypeVo();
    this.props.form.validateFields((err, values) => {
      if (!err) {
        placeTypeVo = Object.assign(placeTypeVo,this.state.placeType,values)
        updatePlaceType(qs.stringify({placeType: placeTypeVo.voToJson()}))
          .then((res) => {
            switch (res.data.serviceResult) {
              case 1:
                notificationShow('success', res.data.resultInfo);
                this.goBack();
                break;
              default:
                notificationShow('error', res.data.resultInfo);
                break;
            }
          })
          .catch((error) => {
            notificationShow('error',error)
          })
      }
    });
  }

  render() {
    const {getFieldDecorator} = this.props.form;
    const formItemLayout = {
      labelCol: {
        xs: {span: 24},
        sm: {span: 6}
      },
      wrapperCol: {
        xs: {span: 24},
        sm: {span: 14}
      }
    };
    return (
      <QueueAnim duration="1200">
        <div key="placeTypeAddiction" style={{padding: 15, background: '#fff'}}>
          <Breadcrumb>
            <Breadcrumb.Item>场地管理</Breadcrumb.Item>
            <Breadcrumb.Item>修改场地类型</Breadcrumb.Item>
          </Breadcrumb>
          <div style={{margin: '15px 0'}}>
            <Popconfirm placement="top" title="您确定放弃当前添加记录吗?" okText="是" cancelText="否"
                        onConfirm={this.goBack}>
              <Button type="default" icon="rollback">返回</Button>
            </Popconfirm>
            <Button type="primary" icon="file" style={{marginLeft: 10}} onClick={this.handleSubmit}>保存</Button>
          </div>
          <div style={{borderTop: '1px dashed #e7eaec', margin: '20px 0'}}/>
          <Form>
            <FormItem {...formItemLayout} label="类型名称" hasFeedback>
              {getFieldDecorator('placeTypeName', {rules: [{required: true, message: '类型名称不能为空'}]})(
                <Input/>
              )}
            </FormItem>
          </Form>
        </div>
      </QueueAnim>
    )
  }

  componentDidMount() {
    let placeTypeVo = new PlaceTypeVo(this.props.match.params.id);
    getOnePlaceType(qs.stringify({placeType:placeTypeVo.voToJson()}))
      .then(res => {
        this.setState({...this.state,placeType:res.data.resultParam})
        this.props.form.setFieldsValue(this.state.placeType)
      })
      .catch(err => {
        notificationShow('error',err)
      })
  }

}

PlaceTypeEditionPage = Form.create()(PlaceTypeEditionPage);

export default PlaceTypeEditionPage

上面所使用的工具类
untils/notification.js

import {notification} from 'antd';
export function notificationShow(type,description) {
  notification[type]({
    message:'提示',
    description:description,
    duration:2
  })
}

添加路由

页面写完了,还要为三个页面配置路由

import App from '../containers/index';
import PlaceTypeListPage from '../containers/PlaceType/PlaceTypeList'
import PlaceTypeAddictionPage from '../containers/PlaceType/PlaceTypeAddiction'
import PlaceTypeEditionPage from '../containers/PlaceType/PlaceTypeEdition'


export default class RouterMap extends React.Component {
  render() {
    return (
      <Router history={history}>
        <App>
          <Route path="/placeTypeList" component={PlaceTypeListPage}/>
          <Route path="/addPlaceType" component={PlaceTypeAddictionPage}/>
          <Route path="/editPlaceType/:id" component={PlaceTypeEditionPage}/>
        </App>
      </Router>)
  }
}

到此场地类型的所有功能就已经完成了,可以启动服务查看。

总结

这里只是对redux进行了简单的使用,了解大致的流程,目录结构的搭建。要想进一步学习,还得继续参考别人的项目,深入学习。结合别人的项目和文档可以更好的学习,有些时候文档可能有些东西你没看到,刚好可以从别人的项目的里面找到。或者文档里面没有说明的东西也可以从别人的项目里面学到,很多代码都要自己尝试出来,作为以后的参考。

小提示:及时把代码上传的Git是个好习惯

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值