zoukankan      html  css  js  c++  java
  • 【后台管理系统】—— 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数组
      
      <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 里面

     

    参考资料


    注:转载请注明出处

  • 相关阅读:
    Memcached的原理分析与配置
    .Net Mvc判断用户是否登陆、未登陆跳回登陆页、三种完美解决方案
    C# ModBus Tcp客户端读取数据 完整Demo
    Log4Net配置
    commons.dbutils1.2介绍及使用
    Java通用分页
    Mysql 通用分页
    JAVA MYSQL做分页
    设置 crossdomain.xml 文件实施 HTTP 流式传输
    flash跨域策略文件crossdomain.xml配置详解
  • 原文地址:https://www.cnblogs.com/ljq66/p/10705933.html
Copyright © 2011-2022 走看看