2018-09-29

安装dva-cli并创建应用


$ npm i dva-cli@next -g
$ dva -v
1.0.0-beta.4

2. 配置代理,能通过restful 的方式访问 [#](#t12. 配置代理,能通过restful 的方式访问)

.webpackrc

{
        "proxy": {
            "/api": {
              "target": "http://localhost:7001",
              "changeOrigin": true,
              "pathRewrite": { "^/api" : "" }
            }
        }
}

http://localhost:8000

3.新建用户路由 #

src/pages/users/page.js

export default () => {
    return (
      <div>
        用户管理
      </div>
    )
  }

http://localhost:8000/users

4. model 和 service [#](#t34. model 和 service)

4.1 src/utils/request.js [#](#t44.1 src/utils/request.js)

export default async function request(url,options) {
  const response=fetch(url,options);
  checkStatus(response);
  const data=await response.json();
  const ret={
    data,
    headers:{}
  }
  if (response.headers.get('x-total-count')) {
    ret.headers['x-total-count']=response.headers.get('x-total-count');
  }
  return ret;
}

4.2 models/users.js [#](#t54.2 models/users.js)

src/pages/users/models/users.js

import * as usersService from '../services/users';
export default {
    namespace: 'users',
    state: {
        list: [],
        total:null
    },
    reducers: {
        save(state,{payload:{data:list,total}}) {
            return {...state,list,total};
        }
    },
    effects: {
        *fetch({payload: {page}},{call,put}) {
            const {data,headers}=yield call(usersService.fetch,{page});
            yield put({type:'save',payload:{data,total:headers['x-total-count']}});
        }
    },
    subscriptions: {
        setup({dispatch,history}) {
            return history.listen((pathname,query) => {
                if (pathname=='/users') {
                    dispatch({type:'fetch',payload:query});
                }
            });
        }
    }
}

4.3 services/users.js [#](#t64.3 services/users.js)

pages/users/services/users.js

import request from '../../../utils/request';
export function fetch(page=1) {
    return request(`/api/users?_page=${page}&_limit=5`);
}

5. 显示用户列表 [#](#t75. 显示用户列表)

5.1 src/pages/users/models/users.js [#](#t85.1 src/pages/users/models/users.js)

import * as usersService from '../services/users';
export default {
    namespace: 'users',
    state: {
        list: [],
        total: null,
        page:1
    },
    reducers: {
        save(state,{payload:{data:list,total,page}}) {
            return {...state,list,total,page};
        }
    },
    effects: {
        *fetch({payload: {page=1}},{call,put}) {
            const {data,headers}=yield call(usersService.fetch,{page});
            yield put({
                type: 'save',payload: {
                    data,
                    total: parseInt(headers['x-total-count'],10),
                    page: parseInt(page,10)
                }
            });
        }
    },
    subscriptions: {
        setup({dispatch,history}) {
            return history.listen(({pathname,query}) => {
                if (pathname=='/users') {
                    dispatch({type:'fetch',payload:query});
                }
            });
        }
    }
}

5.2 src/pages/users/page.js [#](#t95.2 src/pages/users/page.js)

import Users from './components/Users';
export default () => {
    return (
      <Users />
    )
}

5.3 src/pages/users/services/users.js [#](#t105.3 src/pages/users/services/users.js)

import request from '../../../utils/request';
import {PAGE_SIZE} from '../contants';
export function fetch(page=1) {
    return request(`/api/users?_page=${page}&_limit=${PAGE_SIZE}`);
}

5.4 src/utils/request.js [#](#t115.4 src/utils/request.js)

import fetch from 'dva/fetch';

function checkStatus(response) {
  if (response.status >= 200 && response.status < 300) {
    return response;
  }

  const error = new Error(response.statusText);
  error.response = response;
  throw error;
}

/**
 * Requests a URL, returning a promise.
 *
 * @param  {string} url       The URL we want to request
 * @param  {object} [options] The options we want to pass to "fetch"
 * @return {object}           An object containing either "data" or "err"
 */
export default async function request(url,options) {
  const response=await fetch(url,options);
  checkStatus(response);
  const data=await response.json();
  const ret={
    data,
    headers:{}
  }
  if (response.headers.get('x-total-count')) {
    ret.headers['x-total-count']=response.headers.get('x-total-count');
  }
  return ret;
}

5.5 src/pages/users/contants.js [#](#t125.5 src/pages/users/contants.js)

export const PAGE_SIZE = 3;

5.6. src/pages/users/components/Users.js [#](#t135.6. src/pages/users/components/Users.js)

import {connect} from 'dva';
import {Table,Card,Pagination,Popconfirm} from 'antd';
import {PAGE_SIZE} from '../contants';
function Users({list: dataSource,total,page: current,dispatch}) {
    function del(id) {
        console.log('delete ',id);
        dispatch({type:'users/del',payload:id});
    }
    const columns=[
        {
            title: '用户名',
            dataIndex: 'username',
            key: 'username'
        },
        {
            title: '邮箱',
            dataIndex: 'email',
            key: 'email'
        }
        ,
        {
            title: '电话',
            dataIndex: 'phone',
            key: 'phone'
        },
        {
            title: ' 操作',
            key: 'operation',
            render: (text,{id}) => (
                <span>
                    <a href="">编辑</a>
                    <Popconfirm title="请问你确定要删除吗?" onConfirm={()=>del(id)}>
                        <a>删除</a>
                    </Popconfirm>
                </span>
            )
        }]
    return (
        <Card>
            <Table
                columns={columns}
                dataSource={dataSource}
                rowKey={record => record.id}
                pagination={false}
            />
            <Pagination
                className="ant-table-pagination"
                total={total}
                current={current}
                pageSize={PAGE_SIZE}
            />
        </Card>
    )
}
export default connect(
    state => state.users
)(Users);

6. 添加 layout [#](#t146. 添加 layout)

6.1 Header.js [#](#t156.1 Header.js)

import { Menu, Icon } from 'antd';
import Link from 'umi/link';

function Header({ location }) {
  return (
    <Menu
      selectedKeys={[location.pathname]}
      mode="horizontal"
      theme="dark"
    >
      <Menu.Item key="/">
        <Link to="/"><Icon type="home" />Home</Link>
      </Menu.Item>
      <Menu.Item key="/users">
        <Link to="/users"><Icon type="bars" />Users</Link>
      </Menu.Item>
    </Menu>
  );
}

export default Header;

6.2 layouts/index.js [#](#t166.2 layouts/index.js)

layouts/index.js

import React from 'react';
import Header from './Header';
import withRouter from 'umi/withRouter';
function Layout({children,location}) {
    return (
        <div>
            <Header location={location}/>
            {children}
        </div>
    )
}
export default withRouter(Layout);

7. 处理loading状态 [#](#t177. 处理loading状态)

7.1 pages/users/components/Users.js [#](#t187.1 pages/users/components/Users.js)

src/pages/users/components/Users.js

import {connect} from 'dva';
import {Table,Card,Pagination,Popconfirm} from 'antd';
import {PAGE_SIZE} from '../contants';
function Users({list: dataSource,loading,total,page: current,dispatch}) {
    function del(id) {
        console.log('delete ',id);
        dispatch({type:'users/del',payload:id});
    }
    const columns=[
        {
            title: '用户名',
            dataIndex: 'username',
            key: 'username'
        },
        {
            title: '邮箱',
            dataIndex: 'email',
            key: 'email'
        }
        ,
        {
            title: '电话',
            dataIndex: 'phone',
            key: 'phone'
        },
        {
            title: ' 操作',
            key: 'operation',
            render: (text,{id}) => (
                <span>
                    <a href="">编辑</a>
                    <Popconfirm title="请问你确定要删除吗?" onConfirm={()=>del(id)}>
                        <a>删除</a>
                    </Popconfirm>
                </span>
            )
        }]
    return (
        <Card>
            <Table
                columns={columns}
                dataSource={dataSource}
                rowKey={record => record.id}
                pagination={false}
                loading={loading}
            />
            <Pagination
                className="ant-table-pagination"
                total={total}
                current={current}
                pageSize={PAGE_SIZE}
            />
        </Card>
    )
}
export default connect(
    state => ({...state.users,loading:state.loading.models.users})
)(Users);

8. 处理分页 [#](#t198. 处理分页)

8.1 src/pages/users/components/Users.js [#](#t208.1 src/pages/users/components/Users.js)

import {connect} from 'dva';
import {Table,Card,Pagination,Popconfirm} from 'antd';
import {PAGE_SIZE} from '../contants';
import {routerRedux} from 'dva/router';
function Users({list: dataSource,loading,total,page: current,dispatch}) {
    function del(id) {
        console.log('delete ',id);
        dispatch({type:'users/del',payload:id});
    }
    function handlePageChange(page) {
        dispatch(routerRedux.push({
            pathname: '/users',
            query:{page}
        }));
    }
    const columns=[
        {
            title: '用户名',
            dataIndex: 'username',
            key: 'username'
        },
        {
            title: '邮箱',
            dataIndex: 'email',
            key: 'email'
        }
        ,
        {
            title: '电话',
            dataIndex: 'phone',
            key: 'phone'
        },
        {
            title: ' 操作',
            key: 'operation',
            render: (text,{id}) => (
                <span>
                    <a href="">编辑</a>
                    <Popconfirm title="请问你确定要删除吗?" onConfirm={()=>del(id)}>
                        <a>删除</a>
                    </Popconfirm>
                </span>
            )
        }]
    return (
        <Card>
            <Table
                columns={columns}
                dataSource={dataSource}
                rowKey={record => record.id}
                pagination={false}
                loading={loading}
            />
            <Pagination
                className="ant-table-pagination"
                total={total}
                current={current}
                pageSize={PAGE_SIZE}
                onChange={handlePageChange}
            />
        </Card>
    )
}
export default connect(
    state => ({...state.users,loading:state.loading.models.users})
)(Users);

9. 处理用户删除 [#](#t219. 处理用户删除)

9.1 src/pages/users/components/Users.js [#](#t229.1 src/pages/users/components/Users.js)

import {connect} from 'dva';
import {Table,Card,Pagination,Popconfirm} from 'antd';
import {PAGE_SIZE} from '../contants';
import {routerRedux} from 'dva/router';
function Users({list: dataSource,loading,total,page: current,dispatch}) {
    function del(id) {
        dispatch({type:'users/del',payload:id});
    }
    function handlePageChange(page) {
        dispatch(routerRedux.push({
            pathname: '/users',
            query:{page}
        }));
    }
    const columns=[
        {
            title: '用户名',
            dataIndex: 'username',
            key: 'username'
        },
        {
            title: '邮箱',
            dataIndex: 'email',
            key: 'email'
        }
        ,
        {
            title: '电话',
            dataIndex: 'phone',
            key: 'phone'
        },
        {
            title: ' 操作',
            key: 'operation',
            render: (text,{id}) => (
                <span>
                    <a href="">编辑</a>
                    <Popconfirm title="请问你确定要删除吗?" onConfirm={()=>del(id)}>
                        <a>删除</a>
                    </Popconfirm>
                </span>
            )
        }]
    return (
        <Card>
            <Table
                columns={columns}
                dataSource={dataSource}
                rowKey={record => record.id}
                pagination={false}
                loading={loading}
            />
            <Pagination
                className="ant-table-pagination"
                total={total}
                current={current}
                pageSize={PAGE_SIZE}
                onChange={handlePageChange}
            />
        </Card>
    )
}
export default connect(
    state => ({...state.users,loading:state.loading.models.users})
)(Users);

9.2 src/pages/users/models/users.js [#](#t239.2 src/pages/users/models/users.js)

import * as usersService from '../services/users';
export default {
    namespace: 'users',
    state: {
        list: [],
        total: null,
        page:1
    },
    reducers: {
        save(state,{payload:{data:list,total,page}}) {
            return {...state,list,total,page};
        }
    },
    effects: {
        *fetch({payload: {page=1}},{call,put}) {
            const {data,headers}=yield call(usersService.fetch,{page});
            yield put({
                type: 'save',payload: {
                    data,
                    total: parseInt(headers['x-total-count'],10),
                    page: parseInt(page,10)
                }
            });
        },
        *del({payload:id},{call,put,select}) {
            yield call(usersService.del,id);
            const page=yield select(state => state.users.page);
            yield put({type:'fetch',payload:{page}});
        }
    },
    subscriptions: {
        setup({dispatch,history}) {
            return history.listen(({pathname,query}) => {
                if (pathname=='/users') {
                    dispatch({type:'fetch',payload:query});
                }
            });
        }
    }
}

9.3 src/pages/users/services/users.js [#](#t249.3 src/pages/users/services/users.js)

import request from '../../../utils/request';
import {PAGE_SIZE} from '../contants';
export function fetch(page=1) {
    return request(`/api/users?_page=${page}&_limit=${PAGE_SIZE}`);
}
export function del(id) {
    return request(`/api/users/${id}`,{
        method:'DELETE'
    });
}

10. 用户编辑 [#](#t2510. 用户编辑)

10.1 src/pages/users/components/Users.js [#](#t2610.1 src/pages/users/components/Users.js)

src/pages/users/components/Users.js

import React,{Fragment} from 'React';
import {connect} from 'dva';
import {Table,Card,Pagination,Popconfirm,Button} from 'antd';
import {PAGE_SIZE} from '../contants';
import {routerRedux} from 'dva/router';
import UserModal from './UserModal';
function Users({list: dataSource,loading,total,page: current,dispatch}) {
    function del(id) {
        dispatch({type:'users/del',payload:id});
    }
    function handlePageChange(page) {
        dispatch(routerRedux.push({
            pathname: '/users',
            query:{page}
        }));
    }
    function handleUpdate(id,values) {
        values.id=id;
        dispatch({
            type: 'users/update',
            payload:{id,values}
        });
    }
    const columns=[
        {
            title: '用户名',
            dataIndex: 'username',
            key: 'username'
        },
        {
            title: '邮箱',
            dataIndex: 'email',
            key: 'email'
        }
        ,
        {
            title: '电话',
            dataIndex: 'phone',
            key: 'phone'
        },
        {
            title: ' 操作',
            key: 'operation',
            render: (text,record) => (
                <Fragment>
                    <UserModal
                        onOk={handleUpdate.bind(this,record.id)}
                        record={record}>
                        <Button type="warning" style={{marginRight:10}}>编辑</Button>
                    </UserModal>

                    <Popconfirm title="请问你确定要删除吗?" onConfirm={()=>del(record.id)}>
                        <Button type="danger">删除</Button>
                    </Popconfirm>
                </Fragment>
            )
        }]
    return (
        <Card>
            <Table
                columns={columns}
                dataSource={dataSource}
                rowKey={record => record.id}
                pagination={false}
                loading={loading}
            />
            <Pagination
                className="ant-table-pagination"
                total={total}
                current={current}
                pageSize={PAGE_SIZE}
                onChange={handlePageChange}
            />
        </Card>
    )
}
export default connect(
    state => ({...state.users,loading:state.loading.models.users})
)(Users);

10.2 src/pages/users/models/users.js [#](#t2710.2 src/pages/users/models/users.js)

src/pages/users/models/users.js

import * as usersService from '../services/users';
export default {
    namespace: 'users',
    state: {
        list: [],
        total: null,
        page:1
    },
    reducers: {
        save(state,{payload:{data:list,total,page}}) {
            return {...state,list,total,page};
        }
    },
    effects: {
        *fetch({payload: {page=1}},{call,put}) {
            const {data,headers}=yield call(usersService.fetch,{page});
            yield put({
                type: 'save',payload: {
                    data,
                    total: parseInt(headers['x-total-count'],10),
                    page: parseInt(page,10)
                }
            });
        },
        *del({payload:id},{call,put,select}) {
            yield call(usersService.del,id);
            const page=yield select(state => state.users.page);
            yield put({type:'fetch',payload:{page}});
        },
        *update({payload: {id,values}},{call,put,select}) {
            yield call(usersService.update,id,values);
            const page=yield select(state => state.users.page);
            yield put({type:'fetch',payload:{page}});
        }
    },
    subscriptions: {
        setup({dispatch,history}) {
            return history.listen(({pathname,query}) => {
                if (pathname=='/users') {
                    dispatch({type:'fetch',payload:query});
                }
            });
        }
    }
}

10.3 src/pages/users/services/users.js [#](#t2810.3 src/pages/users/services/users.js)

src/pages/users/services/users.js

import request from '../../../utils/request';
import {PAGE_SIZE} from '../contants';
export function fetch(page=1) {
    return request(`/api/users?_page=${page}&_limit=${PAGE_SIZE}`);
}
export function del(id) {
    return request(`/api/users/${id}`,{
        method:'DELETE'
    });
}
export function update(id,values) {
    return request(`/api/users/${id}`,{
        method: 'PUT',
        headers: {
            "Content-Type":"application/json"
        },
        body:JSON.stringify(values)
    });
}

10.4 src/pages/users/components/UserModal.js [#](#t2910.4 src/pages/users/components/UserModal.js)

src/pages/users/components/UserModal.js

import {Component,Fragment} from 'react';
import {Modal,Form,Input,Button} from 'antd';
class UserEditModal extends Component{
    constructor() {
        super();
        this.state={
            visible:false
        }
    }
    handleOk=() => {
        const {onOk}=this.props;
        this.props.form.validateFields((err,values) => {
            if (!err) {
                onOk(values);
                this.setState({visible:false});
            }
        });
    }
    render() {
        const {children}=this.props;
        const {getFieldDecorator}=this.props.form;
        const {username,email,phone}=this.props.record;
        const formItemLayout={
            labelCol: {span: 6},
            wrapperCol:{span:14}
        }
        return (
            <Fragment>
                <span onClick={()=>this.setState({visible:true})}>{children}</span>
                <Modal
                    title="编辑用户"
                    visible={this.state.visible}
                    onOk={this.handleOk}
                    onCancel={()=>this.setState({visible:false})}
                >
                  <Form horizontal onSubmit={this.okHandler}>
                    <Form.Item {...formItemLayout} label="用户名">
                    {
                        getFieldDecorator('username', {
                          initialValue: username,
                        })(<Input />)
                    }
                    </Form.Item>
                    <Form.Item {...formItemLayout} label="邮箱"
                        >
                        {
                            getFieldDecorator('email', {
                            initialValue: email,
                            })(<Input />)
                        }
                    </Form.Item>
                    <Form.Item {...formItemLayout} label="电话">
                        {
                            getFieldDecorator('phone', {
                            initialValue: phone,
                            })(<Input />)
                        }
                    </Form.Item>
                   </Form>
                </Modal>
            </Fragment>
        )
    }
}
export default Form.create()(UserEditModal);

11. 添加用户 [#](#t3011. 添加用户)

11.1 src/pages/users/components/Users.js [#](#t3111.1 src/pages/users/components/Users.js)

src/pages/users/components/Users.js

 import React,{Fragment} from 'React';
import {connect} from 'dva';
import {Table,Card,Pagination,Popconfirm,Button} from 'antd';
import {PAGE_SIZE} from '../contants';
import {routerRedux} from 'dva/router';
import UserModal from './UserModal';
function Users({list: dataSource,loading,total,page: current,dispatch}) {
    function del(id) {
        dispatch({type:'users/del',payload:id});
    }
    function handlePageChange(page) {
        dispatch(routerRedux.push({
            pathname: '/users',
            query:{page}
        }));
    }
    function handleUpdate(id,values) {
        values.id=id;
        dispatch({
            type: 'users/update',
            payload:{id,values}
        });
    }
    function handleCreate(values) {
        dispatch({
            type: 'users/create',
            payload:values
        });
    }
    const columns=[
        {
            title: '用户名',
            dataIndex: 'username',
            key: 'username'
        },
        {
            title: '邮箱',
            dataIndex: 'email',
            key: 'email'
        }
        ,
        {
            title: '电话',
            dataIndex: 'phone',
            key: 'phone'
        },
        {
            title: ' 操作',
            key: 'operation',
            render: (text,record) => (
                <Fragment>
                    <UserModal
                        onOk={handleUpdate.bind(this,record.id)}
                        record={record}>
                        <Button type="warning" style={{marginRight:10}}>编辑</Button>
                    </UserModal>

                    <Popconfirm title="请问你确定要删除吗?" onConfirm={()=>del(record.id)}>
                        <Button type="danger">删除</Button>
                    </Popconfirm>
                </Fragment>
            )
        }]
    return (
        <Card>
            <UserModal
                        onOk={handleCreate}
                        record={{}}>
                        <Button type="warning" style={{marginRight:10}}>添加</Button>
            </UserModal>
            <Table
                columns={columns}
                dataSource={dataSource}
                rowKey={record => record.id}
                pagination={false}
                loading={loading}
            />
            <Pagination
                className="ant-table-pagination"
                total={total}
                current={current}
                pageSize={PAGE_SIZE}
                onChange={handlePageChange}
            />
        </Card>
    )
}
export default connect(
    state => ({...state.users,loading:state.loading.models.users})
)(Users);

11.2 src/pages/users/models/users.js [#](#t3211.2 src/pages/users/models/users.js)

src/pages/users/models/users.js

import * as usersService from '../services/users';
export default {
    namespace: 'users',
    state: {
        list: [],
        total: null,
        page:1
    },
    reducers: {
        save(state,{payload:{data:list,total,page}}) {
            return {...state,list,total,page};
        }
    },
    effects: {
        *fetch({payload: {page=1}},{call,put}) {
            const {data,headers}=yield call(usersService.fetch,{page});
            yield put({
                type: 'save',payload: {
                    data,
                    total: parseInt(headers['x-total-count'],10),
                    page: parseInt(page,10)
                }
            });
        },
        *del({payload:id},{call,put,select}) {
            yield call(usersService.del,id);
            const page=yield select(state => state.users.page);
            yield put({type:'fetch',payload:{page}});
        },
        *update({payload: {id,values}},{call,put,select}) {
            yield call(usersService.update,id,values);
            const page=yield select(state => state.users.page);
            yield put({type:'fetch',payload:{page}});
        },
        *create({payload: values},{call,put,select}) {
            yield call(usersService.create,values);
            const page=yield select(state => state.users.page);
            yield put({type:'fetch',payload:{page}});
        }
    },
    subscriptions: {
        setup({dispatch,history}) {
            return history.listen(({pathname,query}) => {
                if (pathname=='/users') {
                    dispatch({type:'fetch',payload:query});
                }
            });
        }
    }
}

11.3 src/pages/users/services/users.js [#](#t3311.3 src/pages/users/services/users.js)

src/pages/users/services/users.js

export function create(values) {
    return request(`/api/users`,{
        method: 'POST',
        headers: {
            "Content-Type":"application/json"
        },
        body:JSON.stringify(values)
    });
}

参考 #

Gitalking ...

Markdown is supported

Be the first guy leaving a comment!