1.1 增加页面
在src/pages
下创建新的目录MemberList
,然后创建 tsx,less 文件
src
pages
+ MemberList
+ index.tsx
+ index.less
初始化index.tsx
的内容如下:
export default () => {
return <div>会员列表</div>;
};
1.2 配置路由
config/routes.ts
中加入路由(同时也是菜单)信息
routes: [
....
+ {
+ name: '会员列表',
+ icon: 'table',
+ path: '/memberlist',
+ component: './MemberList',
+ },
1.3 添加空的列表组件
把src/pages/MemberList/index.tsx
改成如下的内容:
import React from 'react';
import { PageContainer } from '@ant-design/pro-layout';
import ProTable from '@ant-design/pro-table';
import './index.less';
const MemberList: React.FC = () => {
return (
<PageContainer>
<ProTable
headerTitle={'会员列表'}
//不使用列表上的搜索功能
search={false}
/>
</PageContainer>
);
};
export default MemberList;
此时在页面上可以看到一个空的列表组件
如果没有按照期望的风格进行渲染,你需要重新编译运行程序(一般不需要这样)。
1.4 给列表组件增加数据定义
1.4.1 定义数据类型
新建一个文件src/services/type.d.ts
内容如下:
// @ts-ignore
/* eslint-disable */
declare namespace TYPE {
type QueryResult = {
success: boolean;
errorCode?: number;
errorMessage?: string;
data?: any;
}
type Member = {
id?: number;
realName?: string;
identityNumber?: string;
gender?: number;
birthday?: string;
party?: number;
mobile?: string;
email?: string;
degree?: number;
education?: number;
//salary?: number;
divisionCode?: number;
nationality: number;
}
type MemberList = {
data?: Member[];
/** 列表的内容总数 */
total?: number;
success?: boolean;
};
type PageParams = {
current?: number;
pageSize?: number;
};
}
1.4.2 定义数据列
在src/pages/MemberList/index.tsx增加如下的import语句
import type { ProColumns } from '@ant-design/pro-table';
在MemberList函数的return语句前,定义数据列
const columns: ProColumns<TYPE.Member>[] = [
{
title: '姓名',
dataIndex: 'realName',
valueType: 'text',
}, {
title: '性别',
dataIndex: 'gender',
}, {
title: '出生日期',
dataIndex: 'birthday',
valueType: 'date',
}, {
title: '身份证号',
dataIndex: 'identityNumber',
}, {
title: '手机号码',
dataIndex: 'mobile',
}, {
title: '邮件地址',
dataIndex: 'email',
}, {
title: '年收入',
dataIndex: 'salary',
}
];
1.4.3 在列表组件上使用数据列定义
给 ProTable 增加一个属性
<ProTable
headerTitle={'会员列表'}
//不使用列表上的搜索功能
search={false}
+ rowKey="id"
+ columns={columns}
/>
1.5 实现数据加载显示
1.5.1 数据加载函数
新建一个目录src/services/api
,然后新建文件api/member.ts
内容如下
// @ts-ignore
/* eslint-disable */
import { SortOrder } from 'antd/lib/table/interface';
import { request } from 'umi';
export async function queryAllMember(
params: TYPE.PageParams & {
pageSize?: number | undefined;
current?: number | undefined;
keyword?: string | undefined;
},
sort: Record<string, SortOrder>,
filter: Record<string, React.ReactText[]>,
) {
return request<TYPE.MemberList>('/api/member/queryAll', {
method: 'GET',
params: {
...params,
sort,
filter
},
});
}// @ts-ignore
/* eslint-disable */
import { request } from 'umi';
export async function queryAllMember(
params: TYPE.PageParams & {
pageSize?: number | undefined;
current?: number | undefined;
keyword?: string | undefined;
},
sorter: Record<string, string>,
filter: Record<string, React.ReactText[]>,
) {
return request<TYPE.MemberList>('/api/member/queryAll', {
method: 'GET',
params: {
...params,
sorter,
filter
},
});
}
1.5.2 引入数据加载函数
在src/pages/MemberList/index.tsx中
import { queryAllMember } from '@/services/api/member';
1.5.3 在列表组件上使用加载函数
给 ProTable 增加属性和数据定义
- <ProTable
+ <ProTable<TYPE.Member, TYPE.PageParams>
headerTitle={'会员列表'}
//不使用列表上的搜索功能
search={false}
+ request={queryAllMember}
columns={columns}
/>
1.5.4 出错信息
此时保存以后并且Webpack编译完成后,页面会自动刷新,然后会显示如下的出错信息
这个是完全正常的,因为我们并没有在服务器上提供这个访问接口。接下来我们定义mock方法生成模拟数据用来进行调试(有关mock的相关知识请自行学习)。
1.6 生成模拟数据
1.6.1 生成模拟数据
新建一个文件mock/member.ts
。
注意:mock目录是在应用程序的根目录,和config、src目录同一个级别,它里面的内容是在开发阶段给前端提供模拟API及数据的,并不是目标应用程序的一部分,所以不能放到src目录里面。
import { Request, Response } from 'express';
import moment from 'moment';
// mock memberListDataSource
const genList = (current: number, pageSize: number) => {
const memberListDataSource: TYPE.Member[] = [];
for (let i = 0; i < pageSize; i += 1) {
const index = (current - 1) * 10 + i;
memberListDataSource.push({
id: index,
realName: `张三李四${index}`,
identityNumber: '11010520080808' + Math.floor(Math.random() * 1000).toString().padStart(4,'0'),
gender: Math.floor(Math.random() * 10) % 2 + 1,
birthday: moment(new Date(+(new Date()) - Math.floor(Math.random()*10000000000))).format('YYYY-MM-DD'),
party: i % 9,
mobile: '1' + (Math.floor(Math.random() * 10) % 7 + 3).toString() + Math.floor(Math.random() * 10).toString() + '0137' + Math.floor(Math.random() * 10000).toString().padStart(4,'0'),
email: 'user@abc.com',
degree: Math.floor(Math.random() * 10) % 4,
education: Math.floor(Math.random() * 10) % 4,
salary: Math.floor(Math.random() * 100000000) / 100,
nationality: Math.floor(Math.random() * 1000) % 9,
});
}
memberListDataSource.reverse();
return memberListDataSource;
};
let memberListDataSource = genList(1, 100);
上面的代码生成了100条模拟数据。为让模拟数据看起来不同,应用一些随机数的方法。有了这些数据之后,我们还定义一个API响应函数把数据返回给前端。另外,上面的代码会有unused
的警告,这个不用管他,因为我们马上就要用这些定义。
1.6.2 定义数据返回API函数
在member.ts
的下面继续增加如下的代码
function queryAll(req: Request, res: Response, u: string) {
const { current = 1, pageSize = 10 } = req.query;
let dataSource = [...memberListDataSource].slice(
((current as number) - 1) * (pageSize as number),
(current as number) * (pageSize as number),
);
const result = {
success: true,
data: dataSource,
total: memberListDataSource.length,
pageSize,
current,
};
return res.json(result);
}
export default {
'GET /api/member/queryAll': queryAll,
};
现在在浏览器里面手动把页面刷新一下,应该就可以看到有数据的列表
点击右边的齿轮图标可以设置各列是否显示
1.7 完善数据列表的显示方式
在实际的项目开发中,我们往往并不满足与于直接显示原始数据内容,本节将对数据列表各列进行如下的完善:
- 姓名为必须显示的内容,不在列设置里出现
- 用文字(代码值)显示性别
- 把身份证号和手机号码进行脱敏处理
- 年收入单位改成万元、只显示2位小数、右对齐
- 默认的情况下不显示邮件地址
1.7.1 设置必须显示的内容
在columns
的定义中,为姓名列增加新的属性 ```diff const columns: ProColumns[] = [ { title: '姓名',
dataIndex: 'realName',
valueType: 'text',
- hideInSetting: true,
```
保存后,页面会发生局部刷新,再次点击齿轮图标,会发现下拉菜单中没有了姓名选项
1.7.2 用代码名称替换代码值
给性别列增加关于数据枚举列表的属性 ```diff title: ‘性别’, dataIndex: ‘gender’, - valueEnum: {
- 0: ‘ ‘,
- 1: ‘男’,
- 2: ‘女’,
- },
```
重要说明:
根据当前通行的数据库设计准则,不应为物理数据表定义外键(foreign key),所有外键的关系都由应用程序负责编程约束实现。这么做是因为服务器处理外键的资源消耗非常巨大,尤其不适合分布式的环境。以前使用代码表做外键并且在服务器端生成包含代码文字的数据视图的做法更加剧了对服务器资源的消耗。因此新的项目中,我们应把从代码值到代码文字的翻译工作放在前端完成。1.7.3 对特定数据进行脱敏处理
给身份证号和手机号码的列定义分别增加renderText属性 ```diff { title: ‘身份证号’, dataIndex: ‘identityNumber’, - renderText: (val: string) =>
${val.substr(0,3)}***${val.substr(val.length-3,3)}
, }, { title: ‘手机号码’, dataIndex: ‘mobile’,- renderText: (val: string) =>
${val.substr(0,3)}***${val.substr(val.length-4,4)}
, }, ``` 把数据脱敏处理的工作放在前端有助于减轻服务器的压力,但在透过网络传输的内容还是未经脱敏的,所以这只是一个轻度的安全手段。1.7.4 修改数据显示方法
给年收入的列定义增加renderText和align属性 ```diff title: ‘年收入’, dataIndex: ‘salary’,- align: ‘right’,
- renderText: (val: number) =>
${(val / 10000).toFixed(2)} 万元
, ```1.7.5 默认不显示特定列
实现这个功能略有复杂,需要用到React Hook。相关的基础知识请自行学习。
引入useDate
和 ColumnsState
-import React from 'react';
+import React, { useState } from 'react';
...
-import ProTable from '@ant-design/pro-table';
+import ProTable, {ColumnsState} from '@ant-design/pro-table';
定义所需要的Hook变量及初始值
const MemberList: React.FC = () => {
const [columnsStateMap, setColumnsStateMap] = useState<{[key: string]: ColumnsState;}>({
5: {
show: false,
},
});
...
给ProTable
增加两个属性
<ProTable<TYPE.Member, TYPE.PageParams>
... ...
columnsStateMap={columnsStateMap}
onColumnsStateChange={(map) => setColumnsStateMap(map)}
这时候,我们看到的列表应该是这样的
默认的情况下不显示邮件地址
1.7.6 批量定义列的关键字(key)属性
默认的情况下,每个数据列用它在定义时的顺序(也就是数组下标)来标识,所以上面我们定义邮件地址的状态时用序号5代表邮件地址列。但是这种做法非常不直观,更不易维护,一旦列的顺序发生变化,对列状态的定义也需要随之修改。
ProColumns可以给列设置一个关键字(key)属性,这样在columnsStateMap
就可以用key
的值来定义了。通常我们会把每列key
的值设置为和dataIndex
一样的内容,但这并不是必须的。
手工为每列设置key值较为繁琐,并且容易出错,我们在可以columns定义之后添加如下的代码
columns.forEach(col => col.key = col.dataIndex as string);
这样每一列的key
属性都设置为和dataIndex
一样的内容。
现在我们可以把columnsStateMap
的初始值改成用更直观、易维护的方式
email: {
show: false,
},
1.7.7 改变列表区的显式边距
如下图,现在列表的两边有角度的空白,灰色和白色区域的宽度都是24px,这是多个组件嵌套而造成的,没必要的浪费了页面空间
我们给 src/pages/MemberList/index.less中增加下面的风格定义,可以取消两侧的灰色空间
.ant-pro-page-container-children-content {
margin: 0;
}
1.7.8 改变表头和数据的对齐方式
现在我们再column中给身份证号、性别、出生日期、手机号码这几列加上一个属性align: 'center'
,让这些这些内容宽度固定的列的表头和数据都居中显式。然后我们再index.less中在增加一个风格定义:
.ant-table-thead > tr > th {
text-align: center;
}
这样姓名这样宽度不固定的数据仍旧靠左对齐,但它的表头也居中显式。
总体上这样排版比较好看。
版权说明:本文由北京朗思云网科技股份有限公司原创,向互联网开放全部内容但保留所有权力。