【后台管理系統】—— 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 里面

 

參考資料


注:轉載請注明出處


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM