【后台管理系统】—— Ant Design Pro入门学习&项目实践笔记(二)
前言:上一篇梳理了上手Ant Design Pro需要了解的一些基础知识,这一篇记录一些在开发【后台管理系统】登录注册、数据获取、列表展示等功能时需要注意的地方。
一、与服务器交互的一般步骤
- 代理到后端,配置跨域
- 修改 config/config.js
- 配置 proxy 属性。只要 proxy 和 mock url 不同,是可以共存的。
1234567
proxy: {
'/login'
: {
target:
'http://192.168.1.106:9099'
,
changeOrigin:
true
,
pathRewrite: {
'^/login'
:
''
},
},
}
- 添加要请求的接口
- 修改 services/api.js (可以是service目录下其他自定义文件)
12345678
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目录下其它自定义文件)
1234567891011121314151617181920212223242526
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方法)
123456import
{ connect } from
'dva'
@connect(({ login, loading }) => ({
//login是namespace loading是对应使用的方法
login,
//login是namespace
submitting: loading.effects[
'login/login'
],
//login 命名空间的login请求接口(入口)
}))
-
一般在componentDidMount生命钩子中发送请求获取数据
123456789101112componentDidMount() {
//注意务必先使用dva中的connect建立连接,否则是无法调用props中的dispatch法的
this
.props.dispatch({
//调用model中的方法发起请求,(model的命名空间+方法名)
type:
'mbit/firstRequest'
,
//设置参数
payload:{
args1:
"参数1"
,
args2:
"参数2"
,
},
});
}
-
登录提交等操作方法中发送请求获取数据
1234567891011121314151617handleSubmit = (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
},
});
}
};
-
获取数据后的其它操作
-
显示后台返回的数据
123const {Login: { list },loading} =
this
.props;
//这个就在对应namespace下面list数组,之前存放后台返回数据的list数组
<Table columns={columns} dataSource={list?list.content:[]} rowKey=
"id"
/>
//dataSource里面是通过list获取到的数据
-
跳转路由页面
123import
{ routerRedux } from
'dva/router'
;
yield
put(routerRedux.replace(
'/'
));
-
将获取到的数据存入localStorage
1localStorage.setItem(
'login_token'
, response.data.token);
-
每次请求中带着token 获取localStorage中的token封装进请求头中(修改 request.js 请求文件)
123456789101112let
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
这个插件。12345/*
* src/index.js
*/
import
createLoading from
'dva-loading'
;
app.use(createLoading());
它返回的信息包含了 global、model 和 effect 的异步加载完成情况。数据map一
12345678910111213{
"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
中就可以找到具体的对应操作。123456789101112131415161718192021222324252627282930import
{ 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
1234@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"],如下↓可取多个)
1234567891011//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
123456export
default
{
namespace:
'list'
,
state: {
list: [],
},
-
故,在view中
12render() {
const { list: { list }, loading } =
this
.props;
定义使用时:list: { list } ,含义:实体list下的state类型的list变量
三、项目实践注意点
- 登录注册
- 登录关键点:登录成功后,请求中始终带着存着登录信息的token,在其他功能中,如需要获取用户信息,则直接从token中获取
- 注册关键点:注册必须输入正确的手机验证码,在校验手机号格式正确后就可通过阿里云发送验证码,但是同一号码多次发送,可能会被封号
- 数据获取
- 表格Table组件中的单选/多选,获取当前选中项的列表数据:record
12345
{
title:
'资料审核'
,
dataIndex:
'detailInfo'
,
render: (text, record) => <a onClick={() =>
this
.previewItem(record.id)}>资料详情>></a>
},
- 在所有选中项的列表数据中删选/计算符合条件的数据:selectRows
1234567891011121314151617
//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
1234567
const okHandle = (record) => {
form.validateFields((err, fieldsValue) => {
if
(err)
return
;
form.resetFields();
handleRemark(record, fieldsValue);
});
};
1234567891011121314151617181920212223242526272829303132333435const 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数组中的字段与数据一一对应
- 通常,列表上方有【查询】、【编辑】等操作,在输入查询内容时,要对字符串做 去首尾空格,确保执行查询时为完整无空格字符串
1234567891011121314151617181920212223242526272829303132333435363738
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
1234567891011121314
<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>
-
资源文件需要加统一前缀时,在配置文件中定义方法,应用时直接在数据前调用方法即可
1234567891011121314//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 里面
参考资料
注:转载请注明出处
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· go语言实现终端里的倒计时
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列1:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现
· 【杂谈】分布式事务——高大上的无用知识?