【后台管理系统】—— Ant Design Pro入门学习&项目实践笔记(二)

【后台管理系统】—— Ant Design Pro入门学习&项目实践笔记(二)

前言:上一篇梳理了上手Ant Design Pro需要了解的一些基础知识,这一篇记录一些在开发【后台管理系统】登录注册、数据获取、列表展示等功能时需要注意的地方。


一、与服务器交互的一般步骤

  • 代理到后端,配置跨域
  1. 修改 config/config.js
  2. 配置 proxy 属性。只要 proxy 和 mock url 不同,是可以共存的。
    proxy: {
        '/login': {
            target: 'http://192.168.1.106:9099',
            changeOrigin: true,
            pathRewrite: { '^/login': '' },
        },
    }
    
  • 添加要请求的接口
  1. 修改 services/api.js (可以是service目录下其他自定义文件)
    import request from '@/utils/request';  //ant design pro封装的reques请求文件
    
    export async function fakeAccountLogin(params) {
      return request('/login', {
        method: 'POST',
        body: params
      });
    }  
  • 在modal里面写effect调取接口方法

  1. 修改 model/login.js (可以是model目录下其它自定义文件)
    import { fakeAccountLogin, getFakeCaptcha } from '@/services/api'; //请求的接口方法
    
    namespace: 'login', //要唯一
    
    state: {
        list: []  //后台返回的数据存储在该list中,名字想怎么起怎么起
    },
    
    effects: {
        *login({ payload }, { call, put }) {  //login是界面要调取的接口名称
             const response = yield call(fakeAccountLogin, payload);  //yield call()真正调用接口,传递数据,返回响应的方法
             yield put({                   //一个yield put只能触发一个action
                   type: 'queryList',   //通过调用这个reducer把返回数据传给list
                   payload: response, 
             });
    
             reducers: {
                   queryList(state, action) {
                      return {
                          ...state,
                          list: action.payload,  //这就拿到数据啦
                      };
                   },
             }
        }
    }
    
  • 组件中创建连接

  1. 在 pages/User/Login.js 组件中通过 dva提供的connect高阶组件连接组件和dva,传入namespace(唯一)获得其中的state和effects(dispatch方法)

    import { connect } from 'dva'
    
    @connect(({ login, loading }) => ({   //login是namespace  loading是对应使用的方法
         login,       //login是namespace
         submitting: loading.effects['login/login'],  //login 命名空间的login请求接口(入口)
    })) 
  2. 一般在componentDidMount生命钩子中发送请求获取数据

    componentDidMount() {
     //注意务必先使用dva中的connect建立连接,否则是无法调用props中的dispatch法的
            this.props.dispatch({
                //调用model中的方法发起请求,(model的命名空间+方法名)
                type: 'mbit/firstRequest',
                //设置参数
                payload:{
                      args1:"参数1",
                      args2:"参数2",
                },
            });
    }
  3. 登录提交等操作方法中发送请求获取数据

    handleSubmit = (err, values) => {
            const { type } = this.state;
            if (!err) {
                const { dispatch } = this.props;
                dispatch({
                     type: 'login/login',
                     payload: {
                           login_type: "usernameAndPassword",
                           credentials: {
                                username: values.userName,
                                password: values.password
                           },
                          ...values
                     },
                });
            }
    };
    
  • 获取数据后的其它操作

  1. 显示后台返回的数据

    const {Login: { list },loading} = this.props;  //这个就在对应namespace下面list数组,之前存放后台返回数据的list数组
    
      //dataSource里面是通过list获取到的数据
  2. 跳转路由页面

    import { routerRedux } from 'dva/router';
    
    yield put(routerRedux.replace('/'));
  3. 将获取到的数据存入localStorage

    localStorage.setItem('login_token', response.data.token);
  4. 每次请求中带着token  获取localStorage中的token封装进请求头中(修改 request.js 请求文件)

    let login_token;
    
    newOptions.headers = {
         Accept: 'application/json',
         'Content-Type': 'application/json; charset=utf-8',
         ...newOptions.headers,
    };
          
    if (localStorage.getItem('login_token') != null){
         login_token = localStorage.getItem('login_token');
         newOptions.headers['AuthorizationToken'] = localStorage.getItem('login_token');
    }
    
  5.  

    、关于@connect装饰器

    • 组件写法中调用了 dva 所封装的 react-redux 的 @connect 装饰器
    1. 用来接收绑定的 list 这个 model 对应的 redux store。
    2. 这里的装饰器实际除了 app.state.list 以外还实际接收 app.state.loading 作为参数,这个 loading 的来源是 src/index.js 中调用的 dva-loading这个插件。
      /*
      * src/index.js
      */
      import createLoading from 'dva-loading';
      app.use(createLoading());
      

      它返回的信息包含了 global、model 和 effect 的异步加载完成情况。数据map一

      {
        "global": true,
        "models": {
          "list": false,
          "user": true,
          "rule": false
        },
        "effects": {
          "list/fetch": false,
          "user/fetchCurrent": true,
          "rule/fetch": false
        }
      }
      

      在这里带上 {count: 5} 这个 payload (参数)向 store 进行了一个类型为 list/fetch 的 dispatch,在 src/models/list.js 中就可以找到具体的对应操作。

      import { queryFakeList } from '../services/api';
       
      export default {
        namespace: 'list',
       
        state: {
          list: [],
        },
       
        effects: {
          *fetch({ payload }, { call, put }) {
            const response = yield call(queryFakeList, payload);
            yield put({
              type: 'queryList',
              payload: Array.isArray(response) ? response : [],
            });
          },
          /* ... */
        },
       
        reducers: {
          queryList(state, action) {
            return {
              ...state,
              list: action.payload,
            };
          },
          /* ... */
        },
      };
      
    • View中使用@connect

      @connect(({ list, loading }) => ({
        list,//①
        loading: loading.models.list,//②
      }))
      
    1. connect 有两个参数,mapStateToProps以及mapDispatchToProps,一个将state状态绑定到组件的props,一个将dispatch方法绑定到组件的props

    2. 代码①:将实体list中的state数据绑定到props,注意绑定的是实体list整体,使用时需要list.[state中的具体变量]

    3. 代码②:通过loading将上文“数据map一”中的models的list的key对应的value读取出来。赋值给loading,以方便使用,如表格是否有加载图标(也可以通过key value编写:loading.effects["list/fetch"],如下↓可取多个)

      //pages/Dashboard/WorkPlace.js
      
      @connect(({ user, project, activities, chart, loading }) => ({
        currentUser: user.currentUser,
        project,
        activities,
        chart,
        currentUserLoading: loading.effects['user/fetchCurrent'],
        projectLoading: loading.effects['project/fetchNotice'],
        activitiesLoading: loading.effects['activities/fetchList'],
      }))
      
    • 变量获取

    1. 因,在src/models/list.js

      export default {
        namespace: 'list',
       
        state: {
          list: [],
        },
    2. 故,在view中

      render() {
          const { list: { list }, loading } = this.props;
      

      定义使用时:list: { list }  ,含义:实体list下的state类型的list变量

     

    三、项目实践注意点

    • 登录注册
    1. 登录关键点:登录成功后,请求中始终带着存着登录信息的token,在其他功能中,如需要获取用户信息,则直接从token中获取
    2. 注册关键点:注册必须输入正确的手机验证码,在校验手机号格式正确后就可通过阿里云发送验证码,但是同一号码多次发送,可能会被封号
    • 数据获取
    1. 表格Table组件中的单选/多选,获取当前选中项的列表数据:record
      {
            title: '资料审核',
            dataIndex: 'detailInfo',
            render: (text, record) =>  this.previewItem(record.id)}>资料详情>>
      },  
    2. 在所有选中项的列表数据中删选/计算符合条件的数据:selectRows
      //components/StandardTable/index.js
      
      handleRowSelectChange = (selectedRowKeys, selectedRows) => {
          let { noPayList, payedList, needTotalList } = this.state;
          noPayList = initNoPayList(selectedRows);
          payedList = initPayedList(selectedRows);
          needTotalList = needTotalList.map(item => ({
            ...item,
            total: selectedRows.reduce((sum, val) => sum + parseFloat(val[item.dataIndex], 10), 0),
          }));
          const { onSelectRow } = this.props;
          if (onSelectRow) {
            onSelectRow(selectedRows);
          }
      
          this.setState({ selectedRowKeys, selectedRows, noPayList, payedList, needTotalList });
      };
      

        

    3. 表单Form组件中获取用户提交的数据:fields / fieldsValue
      const okHandle = (record) => {
          form.validateFields((err, fieldsValue) => {
            if (err) return;
            form.resetFields();
            handleRemark(record, fieldsValue);
          });
        };
      注意:要使用Form,必须用Form.create()包裹组件,然后从this.props中获取到Form;通过 form.getFieldDecorator 提交数据
      const CreateForm = Form.create()(props => {
        const { modalVisible, record, form, handleRemark, handleModalVisible } = props;
        const rowObject = {
          minRows: 2, 
          maxRows: 6   
        }  
        const okHandle = (record) => {
          form.validateFields((err, fieldsValue) => {
            if (err) return;
            form.resetFields();
            handleRemark(record, fieldsValue);
          });
        };
        return (
           okHandle(record)}
            onCancel={() => handleModalVisible()}
          >
            
              {form.getFieldDecorator('remark', {
                rules: [{ required: true, message: '请输入至少五个字符的审批备注!', min: 5 }],
              })(