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

时间:2024-03-17 14:05:42

前言:上一篇梳理了上手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数组
    
    <Table columns={columns} dataSource={list?list.content:[]} rowKey="id"/>  //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\');
    }
    

 

、关于@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) => <a onClick={() => this.previewItem(record.id)}>资料详情>></a>
    },  
  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 (
        <Modal
          destroyOnClose
          title="添加备注"
          visible={modalVisible}
          okText="确定"
          cancelText="取消"
          onOk={() => okHandle(record)}
          onCancel={() => handleModalVisible()}
        >
          <FormItem labelCol={{ span: 5 }} wrapperCol={{ span: 15 }}>
            {form.getFieldDecorator(\'remark\', {
              rules: [{ required: true, message: \'请输入至少五个字符的审批备注!\', min: 5 }],
            })(
               <TextArea 
                 autosize={rowObject}
               />
              )}
          </FormItem>
        </Modal>
      );
    });
  • 列表展示
  1. componentDidMount() 组件成功挂载后通过this.props中的dispatch方法传递参数、发送请求、获取数据,完成state数据初始化
  2. 定义 columns 列表项数组,传给Table组件的 column属性,其中 title为列表项标题、dataIndex为与服务端返回数据对应字段、render方法对数据处理后展示
  3. 从this.props中拿到的store中存储的 list (或其它)列表数据,传给Table组件的dataSource,列表才可以将column数组中的字段与数据一一对应
  4. 通常,列表上方有【查询】、【编辑】等操作,在输入查询内容时,要对字符串做 去首尾空格,确保执行查询时为完整无空格字符串
     trimStr = str => {
        return str.replace(/(^\s*)|(\s*$)/g,"");
     }
    
    //查询
    handleSearch = e => {
        e.preventDefault();
    
        const { dispatch, form } = this.props;
    
        form.validateFields((err, fieldsValue) => {
          if (err) return;
    
          let values = {
            ...fieldsValue,
            updatedAt: fieldsValue.updatedAt && fieldsValue.updatedAt.valueOf(),
          };
    
          Object.keys(values).forEach(key => {
            if(values[key]){
              values[key] = this.trimStr(values[key])
            }
          });
    
          this.setState({
            formValues: values,
          });
    
          dispatch({
            type: \'agent/fetch\',
            payload: {
              currentPage: 1,
              pd: {},
              showCount: 10
            },
          });
        });
    };  
  • 其它
  1. 向对象中添加新的属性与属性值:Object[\'属性\'] = 值
  2. 遍历对象修改每一个对象属性:Object.keys(values).forEach(key => { ……})
  3. forEach直接操作原数组,不会返回新值,map会返回新值:在React中根据数据动态循环添加元素,使用map
    <Row gutter={24}>
             { infoPics.map((infoPic, index) => 
                   (<Col key={index} xl={6} lg={12} md={24} sm={24} xs={24}>
                         <div className={styles.infoTitle}>{`代理资料${index+1}`}</div>
                         <div 
                              className={styles.infoPic}
                              style={{ backgroundImage: `url(${infoPic})` }}
                         >
                             { infoPic == \'\' ? <div className={styles.empty}><Empty /></div> : \'\' }
                         </div>
                    </Col>)
                  )
             }   
    </Row>
    

  4. 资源文件需要加统一前缀时,在配置文件中定义方法,应用时直接在数据前调用方法即可

    //util/util.js
    export function setFileHost(){
      return \'http://baidu.com/\';    
    }
    
    //RightContent.js
    import { setFileHost } from \'@/utils/utils\'
    
    <Avatar
            size="small"
            className={styles.avatar}
            src={`${setFileHost()+currentUser.headIcon}`}
            alt="avatar"
     /> 
  5. 后台标题:标题修改是在 src/layouts/BasicLayout.js 中找到 getPageTitle 进行修改
  6. 后台Logo:Logo位于 src/components/SideMenu/SideMenu.js 中,原先的logo是props传过来的,所以我在引用logo文件的时候加了import yhzLogo from \'../../assets/logo.png\';避免参数名重复,另外logo图片文件最好放在src/assets 里面

 

参考资料


注:转载请注明出处