前言:上一篇梳理了上手Ant Design Pro需要了解的一些基礎知識,這一篇記錄一些在開發【后台管理系統】登錄注冊、數據獲取、列表展示等功能時需要注意的地方。
一、與服務器交互的一般步驟
- 代理到后端,配置跨域
- 修改 config/config.js
- 配置 proxy 屬性。只要 proxy 和 mock url 不同,是可以共存的。
proxy: { '/login': { target: 'http://192.168.1.106:9099', changeOrigin: true, pathRewrite: { '^/login': '' }, }, }
- 添加要請求的接口
- 修改 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調取接口方法
- 修改 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, //這就拿到數據啦 }; }, } } }
-
組件中創建連接
-
在 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請求接口(入口) }))
-
一般在componentDidMount生命鈎子中發送請求獲取數據
componentDidMount() { //注意務必先使用dva中的connect建立連接,否則是無法調用props中的dispatch法的 this.props.dispatch({ //調用model中的方法發起請求,(model的命名空間+方法名) type: 'mbit/firstRequest', //設置參數 payload:{ args1:"參數1", args2:"參數2", }, }); }
-
登錄提交等操作方法中發送請求獲取數據
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 }, }); } };
-
獲取數據后的其它操作
-
顯示后台返回的數據
const {Login: { list },loading} = this.props; //這個就在對應namespace下面list數組,之前存放后台返回數據的list數組 <Table columns={columns} dataSource={list?list.content:[]} rowKey="id"/> //dataSource里面是通過list獲取到的數據
-
跳轉路由頁面
import { routerRedux } from 'dva/router'; yield put(routerRedux.replace('/'));
-
將獲取到的數據存入localStorage
localStorage.setItem('login_token', response.data.token);
-
每次請求中帶着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
裝飾器
- 用來接收綁定的
list
這個 model 對應的 redux store。 - 這里的裝飾器實際除了
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,//② }))
-
connect 有兩個參數,mapStateToProps以及mapDispatchToProps,一個將state狀態綁定到組件的props,一個將dispatch方法綁定到組件的props
-
代碼①:將實體list中的state數據綁定到props,注意綁定的是實體list整體,使用時需要list.[state中的具體變量]
-
代碼②:通過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'], }))
-
變量獲取
-
因,在
src/models/list.js
export default { namespace: 'list', state: { list: [], },
-
故,在view中
render() { const { list: { list }, loading } = this.props;
定義使用時:list: { list } ,含義:實體list下的state類型的list變量
三、項目實踐注意點
- 登錄注冊
- 登錄關鍵點:登錄成功后,請求中始終帶着存着登錄信息的token,在其他功能中,如需要獲取用戶信息,則直接從token中獲取
- 注冊關鍵點:注冊必須輸入正確的手機驗證碼,在校驗手機號格式正確后就可通過阿里雲發送驗證碼,但是同一號碼多次發送,可能會被封號
- 數據獲取
- 表格Table組件中的單選/多選,獲取當前選中項的列表數據:record
{ title: '資料審核', dataIndex: 'detailInfo', render: (text, record) => <a onClick={() => this.previewItem(record.id)}>資料詳情>></a> },
- 在所有選中項的列表數據中刪選/計算符合條件的數據: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 }); };
- 表單Form組件中獲取用戶提交的數據:fields / fieldsValue
const okHandle = (record) => { form.validateFields((err, fieldsValue) => { if (err) return; form.resetFields(); handleRemark(record, fieldsValue); }); };
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> ); });
- 列表展示
- componentDidMount() 組件成功掛載后通過this.props中的dispatch方法傳遞參數、發送請求、獲取數據,完成state數據初始化
- 定義 columns 列表項數組,傳給Table組件的 column屬性,其中 title為列表項標題、dataIndex為與服務端返回數據對應字段、render方法對數據處理后展示
- 從this.props中拿到的store中存儲的 list (或其它)列表數據,傳給Table組件的dataSource,列表才可以將column數組中的字段與數據一一對應
- 通常,列表上方有【查詢】、【編輯】等操作,在輸入查詢內容時,要對字符串做 去首尾空格,確保執行查詢時為完整無空格字符串
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 }, }); }); };
- 其它
- 向對象中添加新的屬性與屬性值:Object['屬性'] = 值;
- 遍歷對象修改每一個對象屬性:Object.keys(values).forEach(key => { ……})
- 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>
-
資源文件需要加統一前綴時,在配置文件中定義方法,應用時直接在數據前調用方法即可
//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" />
- 后台標題:標題修改是在 src/layouts/BasicLayout.js 中找到 getPageTitle 進行修改
- 后台Logo:Logo位於 src/components/SideMenu/SideMenu.js 中,原先的logo是props傳過來的,所以我在引用logo文件的時候加了import yhzLogo from '../../assets/logo.png';避免參數名重復,另外logo圖片文件最好放在src/assets 里面
參考資料
注:轉載請注明出處