接下来实现的是场地类型管理模块,先把主页的框架写出来,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是个好习惯