1. 程式人生 > >react+react-router+redux開發體育館管理系統(3)--場地型別管理模組開發

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是個好習慣