#ProList - Advanced List
ProList is built on top of ProTable and can be considered a special variant, presenting data in list form. Its API design is similar to Table, making it easy to switch between Table and List.
#When to use
When you need a standard list presentation, or need to flexibly switch between list and table forms, ProList is the right choice.
#Code demo
#Issue regression scenarios
等待请求完成- 第 0-0 条/总共 0 条
- 1
import type { ActionType, ProColumns } from '@ant-design/pro-components';
import { ProCard, ProList } from '@ant-design/pro-components';
import { Alert, Button, Space, Typography } from 'antd';
import { useRef, useState } from 'react';
type Item = { id: number; title: string; description: string };
const columns: ProColumns<Item>[] = [
{ dataIndex: 'title', title: '标题', listSlot: 'title' },
{ dataIndex: 'description', title: '说明', listSlot: 'description' },
{
title: '操作',
listSlot: 'actions',
render: () => [<a key="edit">编辑</a>],
},
];
export default () => {
const actionRef = useRef<ActionType>();
const [pageInfo, setPageInfo] = useState('等待请求完成');
return (
<ProCard direction="column" ghost gutter={[0, 16]}>
<Alert
type="info"
showIcon
message="ProList 展示与实时分页状态"
description="覆盖操作区 hover、grid itemRender gutter 及 actionRef.pageInfo。"
/>
<ProList<Item>
actionRef={actionRef}
rowKey="id"
columns={columns}
showActions="hover"
grid={{ column: 2, gutter: [16, 24] }}
pagination={{ pageSize: 2 }}
request={async () => ({
success: true,
total: 42,
data: [
{ id: 1, title: '项目 A', description: '悬停显示操作区' },
{ id: 2, title: '项目 B', description: '卡片间距保持一致' },
],
})}
itemRender={(item, index, defaultDom) => (
<ProCard title={`自定义 itemRender ${index + 1}`}>
{defaultDom}
</ProCard>
)}
toolBarRender={() => [
<Space key="page-info">
<Button
onClick={() =>
setPageInfo(JSON.stringify(actionRef.current?.pageInfo))
}
>
读取 pageInfo
</Button>
<Typography.Text code>{pageInfo}</Typography.Text>
</Space>,
]}
/>
</ProCard>
);
};
#Basic usage
import type { ProColumns } from '@ant-design/pro-components';
import { ProList } from '@ant-design/pro-components';
import { Avatar, Space, Tag } from 'antd';
type ProjectItem = {
id: string;
name: string;
description: string;
status: 'active' | 'archived' | 'pending';
owner: {
name: string;
avatar: string;
};
tags: string[];
updatedAt: string;
};
const statusMap = {
active: { text: '进行中', color: 'success' },
archived: { text: '已归档', color: 'default' },
pending: { text: '待启动', color: 'warning' },
};
const dataSource: ProjectItem[] = [
{
id: '1',
name: 'Ant Design Pro',
description: '开箱即用的中台前端/设计解决方案',
status: 'active',
owner: {
name: '书琰',
avatar:
'https://gw.alipayobjects.com/zos/antfincdn/XAosXuNZyF/BiazfanxmamNRoxxVxka.png',
},
tags: ['React', 'TypeScript', 'Ant Design'],
updatedAt: '2024-02-09',
},
{
id: '2',
name: 'ProComponents',
description: '专业级别的中后台组件库',
status: 'active',
owner: {
name: '逄一',
avatar:
'https://gw.alipayobjects.com/zos/antfincdn/efFD%24IOql2/weixintupian_20170331104822.jpg',
},
tags: ['React', 'Components'],
updatedAt: '2024-02-08',
},
{
id: '3',
name: 'UmiJS',
description: '插件化的企业级前端应用框架',
status: 'active',
owner: {
name: '期贤',
avatar:
'https://gw.alipayobjects.com/zos/antfincdn/XAosXuNZyF/BiazfanxmamNRoxxVxka.png',
},
tags: ['Framework', 'React'],
updatedAt: '2024-02-07',
},
{
id: '4',
name: 'Ant Design Mobile',
description: '移动端设计规范和组件库',
status: 'pending',
owner: {
name: '玄霜',
avatar:
'https://gw.alipayobjects.com/zos/antfincdn/efFD%24IOql2/weixintupian_20170331104822.jpg',
},
tags: ['Mobile', 'React'],
updatedAt: '2024-02-06',
},
{
id: '5',
name: 'Ant Design Charts',
description: '简单好用的 React 图表库',
status: 'archived',
owner: {
name: '怀渊',
avatar:
'https://gw.alipayobjects.com/zos/antfincdn/XAosXuNZyF/BiazfanxmamNRoxxVxka.png',
},
tags: ['Charts', 'Visualization'],
updatedAt: '2024-02-05',
},
];
const columns: ProColumns<ProjectItem>[] = [
{
title: '项目名称',
dataIndex: 'name',
listSlot: 'title',
},
{
title: '描述',
dataIndex: 'description',
listSlot: 'description',
},
{
title: '负责人',
dataIndex: ['owner', 'avatar'],
listSlot: 'avatar',
render: (_, record) => (
<Avatar src={record.owner.avatar} alt={record.owner.name} />
),
},
{
title: '状态',
dataIndex: 'status',
listSlot: 'subTitle',
render: (_, record) => (
<Tag color={statusMap[record.status].color}>
{statusMap[record.status].text}
</Tag>
),
},
{
title: '标签',
dataIndex: 'tags',
listSlot: 'content',
render: (_, record) => (
<Space size={4} wrap>
{record.tags.map((tag) => (
<Tag key={tag}>{tag}</Tag>
))}
</Space>
),
},
{
title: '操作',
listSlot: 'actions',
render: () => [<a key="view">查看</a>, <a key="edit">编辑</a>],
},
];
export default () => (
<ProList<ProjectItem>
columns={columns}
dataSource={dataSource}
rowKey="id"
headerTitle="项目列表"
tooltip="这是一个基础的列表示例,展示了 ProList 的基本用法"
/>
);
#Using columns + listSlot (Recommended)
Use columns with the listSlot property to share the same column configuration with ProTable. listSlot specifies which slot of the list item the column maps to (e.g. title, avatar, description).
/**
* 使用 columns + listSlot 的新 API,与 ProTable 兼容
*/
import type { ProColumns } from '@ant-design/pro-components';
import { ProList } from '@ant-design/pro-components';
import { Button, Space, Tag } from 'antd';
import type { Key } from 'react';
import { useState } from 'react';
type DataItem = {
id: string;
name: string;
image: string;
desc: string;
labels: string[];
};
const dataSource: DataItem[] = [
{
id: '1',
name: '智慧零售平台',
image:
'https://gw.alipayobjects.com/zos/antfincdn/efFD%24IOql2/weixintupian_20170331104822.jpg',
desc: 'Ant Design, a design language for background applications, is refined by Ant UED Team',
labels: ['技术专栏', '设计语言'],
},
{
id: '2',
name: 'Ant Design',
image:
'https://gw.alipayobjects.com/zos/antfincdn/efFD%24IOql2/weixintupian_20170331104822.jpg',
desc: '企业级数据看板与可视化分析工具,开箱即用的图表方案',
labels: ['Ant Design', '可视化'],
},
{
id: '3',
name: '云原生微服务框架',
image:
'https://gw.alipayobjects.com/zos/antfincdn/efFD%24IOql2/weixintupian_20170331104822.jpg',
desc: '基于 K8s 的微服务开发与治理框架,支持服务注册与发现',
labels: ['云原生'],
},
{
id: '4',
name: '数据可视化引擎',
image:
'https://gw.alipayobjects.com/zos/antfincdn/efFD%24IOql2/weixintupian_20170331104822.jpg',
desc: '面向企业级中后台的数据可视化解决方案',
labels: ['可视化'],
},
];
/**
* 同一份 columns 可同时用于 ProTable 和 ProList
* ProTable 忽略 listSlot,ProList 使用 listSlot 映射到列表项的各个部分
*/
const columns: ProColumns<DataItem>[] = [
{
title: '名称',
dataIndex: 'name',
listSlot: 'title',
},
{
dataIndex: 'image',
listSlot: 'avatar',
search: false,
},
{
title: '标签',
dataIndex: 'labels',
listSlot: 'subTitle',
search: false,
render: (_, row) => (
<Space size={8}>
{row.labels?.map((label) => (
<Tag color="blue" key={label}>
{label}
</Tag>
))}
</Space>
),
},
{
title: '描述',
dataIndex: 'desc',
listSlot: 'description',
search: false,
},
{
title: '操作',
listSlot: 'actions',
search: false,
render: () => [<a key="edit">编辑</a>, <a key="delete">删除</a>],
},
];
const Demo = () => {
const [expandedRowKeys, setExpandedRowKeys] = useState<readonly Key[]>([]);
return (
<ProList<DataItem>
rowKey="id"
headerTitle="columns API(与 ProTable 兼容)"
toolBarRender={() => [
<Button key="add" type="primary">
新建
</Button>,
]}
columns={columns}
expandable={{
expandedRowKeys,
onExpandedRowsChange: setExpandedRowKeys,
}}
dataSource={dataSource}
/>
);
};
export default () => (
<div style={{ padding: 24 }}>
<Demo />
</div>
);
#Card list (columns + listSlot)
- 第 1-8 条/总共 8 条
- 1
/**
* 卡片模式使用 columns + listSlot,推荐使用新的 columns API
* actions 在卡片模式下默认渲染到 extra 位置
*/
import type { ProColumns } from '@ant-design/pro-components';
import { ProList } from '@ant-design/pro-components';
import { Alert, Tag } from 'antd';
type DataItem = {
id: string;
title: string;
category: string;
description: string;
avatar: string;
downloads: number;
rating: number;
status: 'stable' | 'beta' | 'deprecated';
};
const statusConfig = {
stable: { text: '稳定版', color: 'success' },
beta: { text: '测试版', color: 'processing' },
deprecated: { text: '已废弃', color: 'default' },
};
const data: DataItem[] = [
{
id: '1',
title: 'ProTable',
category: '表格组件',
description: '高级表格组件,支持搜索、筛选、排序等功能',
avatar:
'https://gw.alipayobjects.com/zos/antfincdn/upvrAjAPQX/Logo_Tech%252520UI.svg',
downloads: 125000,
rating: 4.8,
status: 'stable',
},
{
id: '2',
title: 'ProForm',
category: '表单组件',
description: '高级表单组件,简化表单开发流程',
avatar:
'https://gw.alipayobjects.com/zos/antfincdn/FLrTNDvlna/youdaoyun.png',
downloads: 98000,
rating: 4.9,
status: 'stable',
},
{
id: '3',
title: 'ProLayout',
category: '布局组件',
description: '开箱即用的中后台布局方案',
avatar:
'https://gw.alipayobjects.com/zos/antfincdn/aPkFc8Sj7n/method-draw-image.svg',
downloads: 156000,
rating: 4.7,
status: 'stable',
},
{
id: '4',
title: 'ProCard',
category: '卡片组件',
description: '可分割的卡片组件,支持多种布局',
avatar: 'https://gw.alipayobjects.com/zos/antfincdn/ZHrcdLPrvN/empty.svg',
downloads: 67000,
rating: 4.6,
status: 'stable',
},
{
id: '5',
title: 'ProDescriptions',
category: '描述列表',
description: '高级描述列表组件,支持编辑和请求',
avatar:
'https://gw.alipayobjects.com/zos/antfincdn/upvrAjAPQX/Logo_Tech%252520UI.svg',
downloads: 45000,
rating: 4.5,
status: 'stable',
},
{
id: '6',
title: 'ProList',
category: '列表组件',
description: '高级列表组件,支持多种展示模式',
avatar:
'https://gw.alipayobjects.com/zos/antfincdn/FLrTNDvlna/youdaoyun.png',
downloads: 52000,
rating: 4.7,
status: 'stable',
},
{
id: '7',
title: 'ProSkeleton',
category: '骨架屏',
description: '高级骨架屏组件,更好的加载体验',
avatar:
'https://gw.alipayobjects.com/zos/antfincdn/aPkFc8Sj7n/method-draw-image.svg',
downloads: 34000,
rating: 4.4,
status: 'beta',
},
{
id: '8',
title: 'ProField',
category: '字段组件',
description: '统一的字段渲染组件',
avatar: 'https://gw.alipayobjects.com/zos/antfincdn/ZHrcdLPrvN/empty.svg',
downloads: 28000,
rating: 4.3,
status: 'stable',
},
];
export default () => {
const columns: ProColumns<DataItem>[] = [
{
title: '组件名称',
dataIndex: 'title',
listSlot: 'title',
},
{
title: '状态',
dataIndex: 'status',
listSlot: 'subTitle',
render: (_, record) => (
<Tag color={statusConfig[record.status].color}>
{statusConfig[record.status].text}
</Tag>
),
},
{
title: '图标',
dataIndex: 'avatar',
listSlot: 'avatar',
},
{
title: '内容',
listSlot: 'content',
render: (_, record) => (
<div style={{ flex: 1 }}>
<div style={{ marginBottom: 8 }}>{record.description}</div>
<div style={{ color: '#999', fontSize: 12 }}>
{record.category} • {(record.downloads / 1000).toFixed(1)}K 下载 •
⭐ {record.rating}
</div>
</div>
),
},
{
title: '操作',
listSlot: 'actions',
render: () => [<a key="install">安装</a>, <a key="docs">文档</a>],
},
];
return (
<div
style={{
backgroundColor: '#eee',
margin: -24,
padding: 24,
}}
>
<Alert
title="推荐使用 columns + listSlot API"
description="新的 columns API 与 ProTable 保持一致,通过 listSlot 指定数据在列表项中的位置。相比 metas API 更加灵活和易于维护。"
type="info"
showIcon
style={{ marginBottom: 16 }}
/>
<ProList<DataItem>
pagination={{ defaultPageSize: 8, showSizeChanger: false }}
rowSelection={{}}
grid={{ gutter: 16, column: 2 }}
columns={columns}
headerTitle="卡片列表(columns API)"
dataSource={data}
/>
</div>
);
};
#Grid config
<ProList
grid={{ gutter: 8, column: 2 }}
itemCardProps={{ bordered: true, ghost: false }}
...
/>- 第 1-12 条/总共 12 条
- 1
- 12 条/页
import { ProList } from '@ant-design/pro-components';
import { Alert, Card, Col, Row, Segmented, Slider, Switch, Tag } from 'antd';
import { useState } from 'react';
const apps = [
{
id: '1',
name: 'Analytics Pro',
category: '数据分析',
description: '企业级数据分析平台,支持多维度数据分析和可视化展示',
icon: 'https://gw.alipayobjects.com/zos/antfincdn/aPkFc8Sj7n/method-draw-image.svg',
status: 'active',
users: 1280,
},
{
id: '2',
name: 'Cloud Storage',
category: '云存储',
description: '安全可靠的企业云存储服务,支持文件同步和共享',
icon: 'https://gw.alipayobjects.com/zos/antfincdn/FLrTNDvlna/youdaoyun.png',
status: 'active',
users: 3450,
},
{
id: '3',
name: 'Task Manager',
category: '项目管理',
description: '高效的任务和项目管理工具,支持团队协作和进度跟踪',
icon: 'https://gw.alipayobjects.com/zos/antfincdn/ZHrcdLPrvN/empty.svg',
status: 'active',
users: 2150,
},
{
id: '4',
name: 'Email Suite',
category: '企业邮箱',
description: '专业的企业邮件服务,支持大附件和邮件加密',
icon: 'https://gw.alipayobjects.com/zos/antfincdn/aPkFc8Sj7n/method-draw-image.svg',
status: 'beta',
users: 890,
},
{
id: '5',
name: 'Video Conference',
category: '视频会议',
description: '高清视频会议系统,支持屏幕共享和实时字幕',
icon: 'https://gw.alipayobjects.com/zos/antfincdn/FLrTNDvlna/youdaoyun.png',
status: 'active',
users: 5200,
},
{
id: '6',
name: 'HR System',
category: '人力资源',
description: '一体化人力资源管理系统,涵盖招聘、考勤、薪酬等',
icon: 'https://gw.alipayobjects.com/zos/antfincdn/ZHrcdLPrvN/empty.svg',
status: 'active',
users: 1560,
},
{
id: '7',
name: 'CRM Platform',
category: '客户关系',
description: '智能客户关系管理平台,助力销售业绩提升',
icon: 'https://gw.alipayobjects.com/zos/antfincdn/aPkFc8Sj7n/method-draw-image.svg',
status: 'active',
users: 2890,
},
{
id: '8',
name: 'Finance Suite',
category: '财务管理',
description: '专业的财务管理系统,支持多币种和自动对账',
icon: 'https://gw.alipayobjects.com/zos/antfincdn/FLrTNDvlna/youdaoyun.png',
status: 'active',
users: 1120,
},
{
id: '9',
name: 'Workflow Engine',
category: '流程引擎',
description: '灵活的工作流引擎,支持可视化流程设计',
icon: 'https://gw.alipayobjects.com/zos/antfincdn/ZHrcdLPrvN/empty.svg',
status: 'beta',
users: 670,
},
{
id: '10',
name: 'Knowledge Base',
category: '知识库',
description: '企业知识管理系统,支持全文检索和智能推荐',
icon: 'https://gw.alipayobjects.com/zos/antfincdn/aPkFc8Sj7n/method-draw-image.svg',
status: 'active',
users: 1940,
},
{
id: '11',
name: 'Security Center',
category: '安全中心',
description: '企业安全管理中心,实时监控和威胁检测',
icon: 'https://gw.alipayobjects.com/zos/antfincdn/FLrTNDvlna/youdaoyun.png',
status: 'active',
users: 780,
},
{
id: '12',
name: 'API Gateway',
category: '接口网关',
description: '统一的 API 网关服务,支持流量控制和监控',
icon: 'https://gw.alipayobjects.com/zos/antfincdn/ZHrcdLPrvN/empty.svg',
status: 'beta',
users: 450,
},
];
const statusMap: Record<string, { text: string; color: string }> = {
active: { text: '运行中', color: 'success' },
beta: { text: '测试中', color: 'processing' },
};
const data = apps.map((app) => ({
id: app.id,
title: app.name,
subTitle: (
<Tag color={statusMap[app.status].color}>{statusMap[app.status].text}</Tag>
),
actions: [<a key="view">打开</a>, <a key="settings">设置</a>],
avatar: app.icon,
content: (
<div>
<div>{app.description}</div>
<div style={{ color: '#999', fontSize: 12, marginTop: 8 }}>
{app.category} • {app.users.toLocaleString()} 用户
</div>
</div>
),
}));
export default () => {
const [column, setColumn] = useState<number>(2);
const [gutter, setGutter] = useState<number>(8);
const [bordered, setBordered] = useState<boolean>(true);
const [ghost, setGhost] = useState<boolean>(false);
return (
<div style={{ padding: 24, backgroundColor: '#f5f5f5' }}>
<Alert
title="Grid 布局配置"
description="ProList 使用 CSS Grid 实现网格布局,支持自定义列数和间距。通过 grid 属性配置,可以实现响应式的卡片网格布局。"
type="info"
showIcon
style={{ marginBottom: 24 }}
/>
<Card title="配置面板" style={{ marginBottom: 24 }}>
<Row gutter={[24, 24]}>
<Col xs={24} md={12}>
<div>
<div style={{ marginBottom: 8, fontWeight: 500 }}>
列数 (column): {column}
</div>
<Slider
min={1}
max={6}
value={column}
onChange={setColumn}
marks={{
1: '1',
2: '2',
3: '3',
4: '4',
5: '5',
6: '6',
}}
/>
</div>
</Col>
<Col xs={24} md={12}>
<div>
<div style={{ marginBottom: 8, fontWeight: 500 }}>
间距 (gutter): {gutter}px
</div>
<Slider
min={0}
max={48}
step={4}
value={gutter}
onChange={setGutter}
marks={{
0: '0',
8: '8',
16: '16',
24: '24',
32: '32',
48: '48',
}}
/>
</div>
</Col>
<Col xs={24} md={8}>
<div style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
<span style={{ fontWeight: 500 }}>显示边框:</span>
<Switch checked={bordered} onChange={setBordered} />
</div>
</Col>
<Col xs={24} md={8}>
<div style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
<span style={{ fontWeight: 500 }}>幽灵模式:</span>
<Switch checked={ghost} onChange={setGhost} />
</div>
</Col>
<Col xs={24} md={8}>
<div style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
<span style={{ fontWeight: 500 }}>边框:</span>
<Segmented
value={bordered ? 'bordered' : 'borderless'}
onChange={(v) => setBordered(v === 'bordered')}
options={[
{ label: '有边框', value: 'bordered' },
{ label: '无边框', value: 'borderless' },
]}
/>
</div>
</Col>
</Row>
<Alert
title="代码示例"
description={
<pre style={{ margin: 0, fontSize: 12 }}>
{`<ProList
grid={{ gutter: ${gutter}, column: ${column} }}
itemCardProps={{ bordered: ${bordered}, ghost: ${ghost} }}
...
/>`}
</pre>
}
type="success"
style={{ marginTop: 24 }}
/>
</Card>
<ProList<any>
grid={{ gutter, column }}
itemCardProps={{
bordered,
ghost,
}}
ghost={ghost}
pagination={{
defaultPageSize: 12,
showSizeChanger: true,
pageSizeOptions: ['6', '12', '18', '24'],
}}
columns={[
{ dataIndex: 'title', listSlot: 'title' },
{ dataIndex: 'subTitle', listSlot: 'subTitle' },
{ dataIndex: 'avatar', listSlot: 'avatar' },
{ dataIndex: 'content', listSlot: 'content' },
{ dataIndex: 'actions', listSlot: 'actions' },
]}
headerTitle="Grid 布局示例"
tooltip="使用 CSS Grid 实现的响应式卡片布局"
dataSource={data}
/>
</div>
);
};
#Table/List view switching
Use the same columns for both ProTable and ProList. Switch between table and list views seamlessly by toggling the component.
/**
* 同一份 columns 同时用于 ProTable 和 ProList,一键切换视图
*/
import type { ProColumns } from '@ant-design/pro-components';
import { ProList, ProTable } from '@ant-design/pro-components';
import { Radio, Space, Tag } from 'antd';
import { useState } from 'react';
type DataItem = {
id: string;
name: string;
avatar: string;
status: 'open' | 'closed' | 'processing';
labels: string[];
updatedAt: string;
};
const dataSource: DataItem[] = [
{
id: '1',
name: '智慧零售平台',
avatar:
'https://gw.alipayobjects.com/zos/antfincdn/efFD%24IOql2/weixintupian_20170331104822.jpg',
status: 'open',
labels: ['技术专栏', '设计语言'],
updatedAt: '2024-01-15',
},
{
id: '2',
name: 'Ant Design',
avatar:
'https://gw.alipayobjects.com/zos/antfincdn/efFD%24IOql2/weixintupian_20170331104822.jpg',
status: 'processing',
labels: ['Ant Design'],
updatedAt: '2024-02-20',
},
{
id: '3',
name: '云原生微服务框架',
avatar:
'https://gw.alipayobjects.com/zos/antfincdn/efFD%24IOql2/weixintupian_20170331104822.jpg',
status: 'closed',
labels: ['云原生', '可视化'],
updatedAt: '2024-03-10',
},
];
/**
* 这份 columns 同时用于 ProTable 和 ProList
* - ProTable 渲染为表格列
* - ProList 通过 listSlot 映射到列表项各部分
*/
const columns: ProColumns<DataItem>[] = [
{
title: '名称',
dataIndex: 'name',
listSlot: 'title',
},
{
title: '头像',
dataIndex: 'avatar',
listSlot: 'avatar',
search: false,
hideInTable: true,
},
{
title: '标签',
dataIndex: 'labels',
listSlot: 'subTitle',
search: false,
render: (_, row) => (
<Space size={4}>
{row.labels?.map((label) => (
<Tag color="blue" key={label}>
{label}
</Tag>
))}
</Space>
),
},
{
title: '状态',
dataIndex: 'status',
valueType: 'select',
valueEnum: {
open: { text: '未解决', status: 'Error' },
closed: { text: '已解决', status: 'Success' },
processing: { text: '处理中', status: 'Processing' },
},
},
{
title: '更新时间',
dataIndex: 'updatedAt',
valueType: 'date',
search: false,
listSlot: 'description',
},
{
title: '操作',
listSlot: 'actions',
search: false,
render: () => [<a key="edit">编辑</a>, <a key="delete">删除</a>],
},
];
export default () => {
const [viewMode, setViewMode] = useState<'list' | 'table'>('list');
return (
<div style={{ padding: 24 }}>
<Radio.Group
value={viewMode}
onChange={(e) => setViewMode(e.target.value)}
style={{ marginBottom: 16 }}
optionType="button"
buttonStyle="solid"
options={[
{ label: '列表视图', value: 'list' },
{ label: '表格视图', value: 'table' },
]}
/>
{viewMode === 'list' ? (
<ProList<DataItem>
rowKey="id"
headerTitle="列表视图"
columns={columns}
dataSource={dataSource}
/>
) : (
<ProTable<DataItem>
rowKey="id"
headerTitle="表格视图"
columns={columns}
dataSource={dataSource}
search={false}
pagination={false}
/>
)}
</div>
);
};
#Search list (columns + request)
- 第 0-0 条/总共 0 条
- 1
/**
* columns API 配合 request 和搜索表单
*/
import type { ProColumns } from '@ant-design/pro-components';
import { ProList } from '@ant-design/pro-components';
import { Button, Space, Tag } from 'antd';
import request from 'umi-request';
type GithubIssueItem = {
url: string;
id: number;
number: number;
title: string;
labels: {
name: string;
color: string;
}[];
state: string;
comments: number;
created_at: string;
updated_at: string;
closed_at?: string;
};
const columns: ProColumns<GithubIssueItem>[] = [
{
title: '用户',
dataIndex: 'user',
listSlot: 'title',
},
{
dataIndex: 'avatar',
listSlot: 'avatar',
search: false,
},
{
dataIndex: 'title',
listSlot: 'description',
search: false,
},
{
dataIndex: 'labels',
listSlot: 'subTitle',
search: false,
render: (_, row) => (
<Space size={8}>
{row.labels?.map((label: { name: string }) => (
<Tag color="blue" key={label.name}>
{label.name}
</Tag>
))}
</Space>
),
},
{
listSlot: 'actions',
search: false,
render: (_, row) => [
<a href={row.url} target="_blank" rel="noopener noreferrer" key="link">
链路
</a>,
<a href={row.url} target="_blank" rel="noopener noreferrer" key="view">
查看
</a>,
],
},
{
title: '状态',
dataIndex: 'state',
valueType: 'select',
valueEnum: {
all: { text: '全部', status: 'Default' },
open: { text: '未解决', status: 'Error' },
closed: { text: '已解决', status: 'Success' },
},
},
];
const Demo = () => (
<ProList<GithubIssueItem>
toolBarRender={() => [
<Button key="add" type="primary">
新建
</Button>,
]}
search={{ filterType: 'light' }}
rowKey="id"
headerTitle="搜索列表(columns API)"
request={async (params = {} as Record<string, any>) =>
request<{ data: GithubIssueItem[] }>(
'https://proapi.azurewebsites.net/github/issues',
{ params },
)
}
pagination={{ pageSize: 5 }}
columns={columns}
/>
);
export default () => (
<div style={{ padding: 24 }}>
<Demo />
</div>
);
#Editable list (columns)
/**
* columns API 配合可编辑列表
*/
import type { ProColumns } from '@ant-design/pro-components';
import { ProList } from '@ant-design/pro-components';
import { Space, Tag } from 'antd';
import { useState } from 'react';
const defaultData = [
{
id: '1',
name: '智慧零售平台',
image:
'https://gw.alipayobjects.com/zos/antfincdn/efFD%24IOql2/weixintupian_20170331104822.jpg',
desc: '我是一条测试的描述',
},
{
id: '2',
name: 'Ant Design',
image:
'https://gw.alipayobjects.com/zos/antfincdn/efFD%24IOql2/weixintupian_20170331104822.jpg',
desc: '我是一条测试的描述',
},
{
id: '3',
name: '云原生微服务框架',
image:
'https://gw.alipayobjects.com/zos/antfincdn/efFD%24IOql2/weixintupian_20170331104822.jpg',
desc: '我是一条测试的描述',
},
];
type DataItem = (typeof defaultData)[number];
const columns: ProColumns<DataItem>[] = [
{
title: '名称',
dataIndex: 'name',
listSlot: 'title',
},
{
dataIndex: 'image',
listSlot: 'avatar',
editable: false,
},
{
title: '描述',
dataIndex: 'desc',
listSlot: 'description',
},
{
listSlot: 'subTitle',
search: false,
editable: false,
render: () => (
<Space size={8}>
<Tag color="blue">Ant Design</Tag>
<Tag color="#5BD8A6">可视化</Tag>
</Space>
),
},
{
listSlot: 'actions',
search: false,
render: (_, row, __, action) => [
<a
onClick={() => {
action?.startEditable(row.id);
}}
key="edit"
>
编辑
</a>,
],
},
];
const Demo = () => {
const [dataSource, setDataSource] = useState<DataItem[]>(defaultData);
return (
<ProList<DataItem>
rowKey="id"
headerTitle="可编辑列表(columns API)"
dataSource={dataSource}
editable={{
onSave: async (key, record, originRow) => {
console.log(key, record, originRow);
return true;
},
}}
onDataSourceChange={setDataSource}
columns={columns}
/>
);
};
export default () => (
<div style={{ padding: 24 }}>
<Demo />
</div>
);
#Edit list
import { ProList } from '@ant-design/pro-components';
import { Space, Tag } from 'antd';
import { useState } from 'react';
const defaultData = [
{
id: '1',
name: '智慧零售平台',
image:
'https://gw.alipayobjects.com/zos/antfincdn/efFD%24IOql2/weixintupian_20170331104822.jpg',
desc: '我是一条测试的描述',
},
{
id: '2',
name: 'Ant Design',
image:
'https://gw.alipayobjects.com/zos/antfincdn/efFD%24IOql2/weixintupian_20170331104822.jpg',
desc: '我是一条测试的描述',
},
{
id: '3',
name: '云原生微服务框架',
image:
'https://gw.alipayobjects.com/zos/antfincdn/efFD%24IOql2/weixintupian_20170331104822.jpg',
desc: '我是一条测试的描述',
},
{
id: '4',
name: '数据可视化引擎',
image:
'https://gw.alipayobjects.com/zos/antfincdn/efFD%24IOql2/weixintupian_20170331104822.jpg',
desc: '我是一条测试的描述',
},
];
type DataItem = (typeof defaultData)[number];
const Demo = () => {
const [dataSource, setDataSource] = useState<DataItem[]>(defaultData);
return (
<ProList<DataItem>
rowKey="id"
headerTitle="基础列表"
dataSource={dataSource}
editable={{
onSave: async (_key, _record, _originRow) => {
return true;
},
}}
onDataSourceChange={setDataSource}
columns={[
{ dataIndex: 'name', listSlot: 'title' },
{ dataIndex: 'image', listSlot: 'avatar', editable: false },
{ dataIndex: 'desc', listSlot: 'description' },
{
listSlot: 'subTitle',
render: () => (
<Space size={8}>
<Tag color="blue">Ant Design</Tag>
<Tag color="#5BD8A6">可视化</Tag>
</Space>
),
},
{
listSlot: 'actions',
render: (text, row, index, action) => [
<a
onClick={() => {
action?.startEditable(row.id);
}}
key="link"
>
编辑
</a>,
],
},
]}
/>
);
};
export default () => (
<div style={{ padding: 24 }}>
<Demo />
</div>
);
#List with toolbar
import type { ActionType } from '@ant-design/pro-components';
import { ProList } from '@ant-design/pro-components';
import { Badge, Button } from 'antd';
import React, { useRef, useState } from 'react';
const dataSource = [
{
name: '实验名称1',
desc: '系统性的沉淀B端知识体系',
content: [
{
label: '模型数',
value: 2903,
},
{
label: '指标数',
value: 3720,
},
{
label: '实验状态',
value: '成功',
status: 'success',
},
],
},
{
name: '实验名称2',
desc: '系统性的沉淀B端知识体系',
content: [
{
label: '模型数',
value: 2904,
},
{
label: '指标数',
value: 3721,
},
{
label: '实验状态',
value: '成功',
status: 'success',
},
],
},
{
name: '实验名称3',
desc: '系统性的沉淀B端知识体系',
content: [
{
label: '模型数',
value: 2905,
},
{
label: '指标数',
value: 3722,
},
{
label: '实验状态',
value: '成功',
status: 'success',
},
],
},
];
const renderBadge = (count: number, active = false) => {
return (
<Badge
count={count}
style={{
marginBlockStart: -2,
marginInlineStart: 4,
color: active ? '#1890FF' : '#999',
backgroundColor: active ? '#E6F7FF' : '#eee',
}}
/>
);
};
const Demo = () => {
const [activeKey, setActiveKey] = useState<React.Key | undefined>('tab1');
const action = useRef<ActionType>();
return (
<ProList<any>
rowKey="name"
actionRef={action}
dataSource={dataSource}
editable={{}}
columns={[
{
dataIndex: 'name',
listSlot: 'title',
valueType: 'select',
fieldProps: {
showSearch: true,
placement: 'bottomRight',
options: [{ label: '实验名称1', value: '实验名称1' }],
},
},
{ dataIndex: 'desc', listSlot: 'description' },
{
dataIndex: 'content',
listSlot: 'content',
render: (text) => (
<div
key="label"
style={{ display: 'flex', justifyContent: 'space-around' }}
>
{(text as any[]).map((t) => (
<div key={t.label}>
<div>{t.label}</div>
<div>
{t.status === 'success' && (
<span
style={{
display: 'inline-block',
width: 8,
height: 8,
borderRadius: '50%',
backgroundColor: '#52c41a',
marginInlineEnd: 8,
}}
/>
)}
{t.value}
</div>
</div>
))}
</div>
),
},
{
listSlot: 'actions',
render: (text, row) => [
<a
key="edit"
onClick={() => action.current?.startEditable(row.name)}
>
编辑
</a>,
<a key="copy">复制</a>,
<a key="delete">删除</a>,
],
},
]}
toolbar={{
menu: {
activeKey,
items: [
{
key: 'tab1',
label: (
<span>全部实验室{renderBadge(99, activeKey === 'tab1')}</span>
),
},
{
key: 'tab2',
label: (
<span>
我创建的实验室{renderBadge(32, activeKey === 'tab2')}
</span>
),
},
],
onChange(key) {
setActiveKey(key);
},
},
search: {
onSearch: (value: string) => {
alert(value);
},
},
actions: [
<Button type="primary" key="primary">
新建实验
</Button>,
],
}}
/>
);
};
export default () => (
<div style={{ padding: 24 }}>
<Demo />
</div>
);
#Expandable list
import { ProList } from '@ant-design/pro-components';
import { Button, Progress, Space, Tag } from 'antd';
import type { Key } from 'react';
import { useState } from 'react';
const dataSource = [
{
title: '智慧零售平台',
avatar:
'https://gw.alipayobjects.com/zos/antfincdn/efFD%24IOql2/weixintupian_20170331104822.jpg',
},
{
title: 'Ant Design',
avatar:
'https://gw.alipayobjects.com/zos/antfincdn/efFD%24IOql2/weixintupian_20170331104822.jpg',
},
{
title: '云原生微服务框架',
avatar:
'https://gw.alipayobjects.com/zos/antfincdn/efFD%24IOql2/weixintupian_20170331104822.jpg',
},
{
title: '数据可视化引擎',
avatar:
'https://gw.alipayobjects.com/zos/antfincdn/efFD%24IOql2/weixintupian_20170331104822.jpg',
},
];
const Demo = () => {
const [expandedRowKeys, setExpandedRowKeys] = useState<readonly Key[]>([]);
return (
<ProList<{ title: string }>
rowKey="title"
headerTitle="支持展开的列表"
toolBarRender={() => {
return [
<Button key="3" type="primary">
新建
</Button>,
];
}}
expandable={{ expandedRowKeys, onExpandedRowsChange: setExpandedRowKeys }}
dataSource={dataSource}
columns={[
{ dataIndex: 'title', listSlot: 'title' },
{
listSlot: 'subTitle',
render: () => (
<Space size={8}>
<Tag color="blue">Ant Design</Tag>
<Tag color="#5BD8A6">可视化</Tag>
</Space>
),
},
{
listSlot: 'description',
render: () => '面向企业级中后台的设计解决方案',
},
{ dataIndex: 'avatar', listSlot: 'avatar' },
{
listSlot: 'content',
render: () => (
<div
style={{
minWidth: 200,
flex: 1,
display: 'flex',
justifyContent: 'flex-end',
}}
>
<div
style={{
width: '200px',
}}
>
<div>发布中</div>
<Progress percent={80} />
</div>
</div>
),
},
{
listSlot: 'actions',
render: () => <a key="invite">邀请</a>,
},
]}
/>
);
};
export default () => (
<div style={{ padding: 24 }}>
<Demo />
</div>
);
#List with row selection
import { ProList } from '@ant-design/pro-components';
import { Button, Progress } from 'antd';
import type { Key } from 'react';
import { useState } from 'react';
const dataSource = [
{
title: '智慧零售平台',
avatar:
'https://gw.alipayobjects.com/zos/antfincdn/efFD%24IOql2/weixintupian_20170331104822.jpg',
},
{
title: 'Ant Design',
avatar:
'https://gw.alipayobjects.com/zos/antfincdn/efFD%24IOql2/weixintupian_20170331104822.jpg',
},
{
title: '云原生微服务框架',
avatar:
'https://gw.alipayobjects.com/zos/antfincdn/efFD%24IOql2/weixintupian_20170331104822.jpg',
},
{
title: '数据可视化引擎',
avatar:
'https://gw.alipayobjects.com/zos/antfincdn/efFD%24IOql2/weixintupian_20170331104822.jpg',
},
];
const Demo = () => {
const [selectedRowKeys, setSelectedRowKeys] = useState<Key[]>([]);
const rowSelection = {
selectedRowKeys,
onChange: (keys: Key[]) => setSelectedRowKeys(keys),
};
return (
<ProList<{ title: string }>
toolBarRender={() => {
return [
<Button key="3" type="primary">
新建
</Button>,
];
}}
columns={[
{ dataIndex: 'title', listSlot: 'title' },
{
listSlot: 'description',
render: () => '面向企业级中后台的设计解决方案',
},
{ dataIndex: 'avatar', listSlot: 'avatar' },
{
listSlot: 'content',
render: () => (
<div
style={{
width: '200px',
}}
>
<div>发布中</div>
<Progress percent={80} />
</div>
),
},
{
listSlot: 'actions',
render: () => [
<a key="init">邀请</a>,
<Button key="publish" type="text">
发布
</Button>,
],
},
]}
rowKey="title"
headerTitle="支持选中的列表"
rowSelection={rowSelection}
dataSource={dataSource}
/>
);
};
export default () => (
<div style={{ padding: 24 }}>
<Demo />
</div>
);
#Search list
- 第 0-0 条/总共 0 条
- 1
import { ProList } from '@ant-design/pro-components';
import { Button, Space, Tag } from 'antd';
import request from 'umi-request';
type GithubIssueItem = {
url: string;
id: number;
number: number;
title: string;
labels: {
name: string;
color: string;
}[];
state: string;
comments: number;
created_at: string;
updated_at: string;
closed_at?: string;
};
const Demo = () => (
<ProList<GithubIssueItem>
toolBarRender={() => {
return [
<Button key="3" type="primary">
新建
</Button>,
];
}}
search={{}}
rowKey="name"
headerTitle="基础列表"
request={async (params = {} as Record<string, any>) =>
request<{
data: GithubIssueItem[];
}>('https://proapi.azurewebsites.net/github/issues', {
params,
})
}
pagination={{
pageSize: 5,
}}
columns={[
{ dataIndex: 'user', listSlot: 'title', title: '用户' },
{ dataIndex: 'avatar', listSlot: 'avatar', search: false },
{ dataIndex: 'title', listSlot: 'description', search: false },
{
dataIndex: 'labels',
listSlot: 'subTitle',
search: false,
render: (_, row) => (
<Space size={0}>
{row.labels?.map((label: { name: string }) => (
<Tag color="blue" key={label.name}>
{label.name}
</Tag>
))}
</Space>
),
},
{
listSlot: 'actions',
search: false,
render: (text, row) => [
<a
href={row.url}
target="_blank"
rel="noopener noreferrer"
key="link"
>
详情
</a>,
<a
href={row.url}
target="_blank"
rel="noopener noreferrer"
key="view"
>
查看
</a>,
],
},
{
title: '状态',
dataIndex: 'status',
valueType: 'select',
valueEnum: {
all: { text: '全部', status: 'Default' },
open: { text: '未解决', status: 'Error' },
closed: { text: '已解决', status: 'Success' },
processing: { text: '解决中', status: 'Processing' },
},
},
]}
/>
);
export default () => (
<div style={{ padding: 24 }}>
<Demo />
</div>
);
#List with filtering and asynchronous requests
- 第 0-0 条/总共 0 条
- 1
import { ProList } from '@ant-design/pro-components';
import { Button, Space, Tag } from 'antd';
import request from 'umi-request';
type GithubIssueItem = {
url: string;
id: number;
number: number;
title: string;
labels: {
name: string;
color: string;
}[];
state: string;
comments: number;
created_at: string;
updated_at: string;
closed_at?: string;
};
const Demo = () => (
<ProList<GithubIssueItem>
toolBarRender={() => {
return [
<Button key="3" type="primary">
新建
</Button>,
];
}}
search={{
filterType: 'light',
}}
rowKey="name"
headerTitle="基础列表"
request={async (params = {} as Record<string, any>) =>
request<{
data: GithubIssueItem[];
}>('https://proapi.azurewebsites.net/github/issues', {
params,
})
}
pagination={{
pageSize: 5,
}}
columns={[
{ dataIndex: 'user', listSlot: 'title', title: '用户' },
{ dataIndex: 'avatar', listSlot: 'avatar', search: false },
{ dataIndex: 'title', listSlot: 'description', search: false },
{
dataIndex: 'labels',
listSlot: 'subTitle',
search: false,
render: (_, row) => (
<Space size={8}>
{row.labels?.map((label: { name: string }) => (
<Tag color="blue" key={label.name}>
{label.name}
</Tag>
))}
</Space>
),
},
{
listSlot: 'actions',
search: false,
render: (text, row) => [
<a
href={row.url}
target="_blank"
rel="noopener noreferrer"
key="link"
>
详情
</a>,
<a
href={row.url}
target="_blank"
rel="noopener noreferrer"
key="view"
>
查看
</a>,
],
},
{
title: '状态',
dataIndex: 'status',
valueType: 'select',
valueEnum: {
all: { text: '全部', status: 'Default' },
open: { text: '未解决', status: 'Error' },
closed: { text: '已解决', status: 'Success' },
processing: { text: '解决中', status: 'Processing' },
},
},
]}
/>
);
export default () => (
<div style={{ padding: 24 }}>
<Demo />
</div>
);
#Size and divider
import { ProList } from '@ant-design/pro-components';
import { Button, Progress, Select, Tag } from 'antd';
import type { Key } from 'react';
import { useState } from 'react';
type ProjectItem = {
title: string;
avatar: string;
description: string;
progress: number;
status: string;
};
const dataSource: ProjectItem[] = [
{
title: '智慧零售平台',
avatar:
'https://gw.alipayobjects.com/zos/antfincdn/efFD%24IOql2/weixintupian_20170331104822.jpg',
description: '面向线下门店的数字化经营解决方案',
progress: 85,
status: '开发中',
},
{
title: 'Ant Design Pro',
avatar:
'https://gw.alipayobjects.com/zos/antfincdn/efFD%24IOql2/weixintupian_20170331104822.jpg',
description: '开箱即用的中台前端解决方案',
progress: 100,
status: '已上线',
},
{
title: '云原生微服务框架',
avatar:
'https://gw.alipayobjects.com/zos/antfincdn/efFD%24IOql2/weixintupian_20170331104822.jpg',
description: '基于 K8s 的微服务开发与治理框架',
progress: 92,
status: '测试中',
},
{
title: '数据可视化引擎',
avatar:
'https://gw.alipayobjects.com/zos/antfincdn/efFD%24IOql2/weixintupian_20170331104822.jpg',
description: '企业级数据看板与图表分析工具',
progress: 60,
status: '开发中',
},
];
const Demo = () => {
const [selectedRowKeys, setSelectedRowKeys] = useState<Key[]>([]);
const [expandedRowKeys, setExpandedRowKeys] = useState<readonly Key[]>([]);
const [size, setSize] = useState<'small' | 'default' | 'large' | undefined>(
'default',
);
const [split, setSplit] = useState<0 | 1>(1);
const rowSelection = {
selectedRowKeys,
onChange: (keys: Key[]) => setSelectedRowKeys(keys),
};
return (
<>
大小:
<Select<string>
value={size}
onChange={(value) => setSize(value as any)}
options={['small', 'default', 'large'].map((selectSize) => ({
value: selectSize,
label: selectSize,
}))}
/>{' '}
分割线:
<Select<0 | 1>
value={split}
onChange={(value) => setSplit(value)}
options={[
{ value: 1, label: '有' },
{ value: 0, label: '无' },
]}
/>{' '}
<br />
<br />
<ProList<ProjectItem>
size={size}
split={split === 1}
toolBarRender={() => {
return [
<Button key="new" type="primary">
新建
</Button>,
];
}}
columns={[
{ dataIndex: 'title', listSlot: 'title' },
{ dataIndex: 'description', listSlot: 'description' },
{ dataIndex: 'avatar', listSlot: 'avatar' },
{
listSlot: 'content',
render: (_, record) => (
<div
style={{
minWidth: 200,
flex: 1,
display: 'flex',
justifyContent: 'flex-end',
}}
>
<div style={{ width: 200 }}>
<div>
{record.status}{' '}
<Tag
color={record.progress === 100 ? 'success' : 'processing'}
>
{record.progress}%
</Tag>
</div>
<Progress percent={record.progress} showInfo={false} />
</div>
</div>
),
},
{
listSlot: 'actions',
render: () => [<a key="invite">邀请</a>],
},
]}
expandable={{
expandedRowKeys,
defaultExpandAllRows: false,
onExpandedRowsChange: setExpandedRowKeys,
}}
rowKey="title"
headerTitle="大小和分割线"
rowSelection={rowSelection}
dataSource={dataSource}
/>
</>
);
};
export default () => (
<div style={{ padding: 24 }}>
<Demo />
</div>
);
#Vertical style
智慧零售平台

Ant Design

云原生微服务框架

数据可视化引擎

import { LikeOutlined, MessageOutlined, StarOutlined } from '@ant-design/icons';
import { ProList } from '@ant-design/pro-components';
import { Button, Space, Tag } from 'antd';
import React from 'react';
const IconText = ({ icon, text }: { icon: any; text: string }) => (
<span>
{React.createElement(icon, { style: { marginInlineEnd: 8 } })}
{text}
</span>
);
const dataSource = [
{
title: '智慧零售平台',
},
{
title: 'Ant Design',
},
{
title: '云原生微服务框架',
},
{
title: '数据可视化引擎',
},
];
const Demo = () => {
return (
<ProList<{ title: string }>
toolBarRender={() => {
return [
<Button key="3" type="primary">
新建
</Button>,
];
}}
itemLayout="vertical"
rowKey="id"
headerTitle="竖排样式"
dataSource={dataSource}
columns={[
{ dataIndex: 'title', listSlot: 'title' },
{
listSlot: 'description',
render: () => (
<Space orientation="horizontal">
<Tag>技术专栏</Tag>
<Tag>设计语言</Tag>
<Tag>云原生</Tag>
</Space>
),
},
{
listSlot: 'actions',
render: () => [
<IconText icon={StarOutlined} text="156" key="star" />,
<IconText icon={LikeOutlined} text="156" key="like" />,
<IconText icon={MessageOutlined} text="2" key="message" />,
],
},
{
listSlot: 'aside',
render: () => (
<img
width={272}
alt="logo"
src="https://gw.alipayobjects.com/zos/rmsportal/mqaQswcyDLcXyDKnZfES.png"
/>
),
},
{
listSlot: 'content',
render: () => (
<div>
面向企业级中后台的数据可视化解决方案,以最小的接入成本提供丰富的图表类型与交互能力,助力业务快速搭建数据看板与分析报表。
</div>
),
},
]}
/>
);
};
export default () => (
<div style={{ padding: 24 }}>
<Demo />
</div>
);
#Some preset modes
import { EllipsisOutlined } from '@ant-design/icons';
import { ProList } from '@ant-design/pro-components';
import { Button, Progress, Tag } from 'antd';
import React, { Key, useState } from 'react';
const types = ['top', 'inline', 'new'];
const data = [
'智慧零售平台(top)',
'Ant Design Pro(inline)',
'云原生微服务框架(new)',
'数据可视化引擎',
].map((item, index) => ({
title: item,
subTitle: <Tag color="#5BD8A6">技术专栏</Tag>,
actions: [
<a key="invite">邀请</a>,
<a key="operate">操作</a>,
<a key="rest">
<EllipsisOutlined />
</a>,
],
description: (
<div>
<div>top 会有小角标</div>
<div>inline 标题字体是 normal</div>
<div>new 会有一个入场动画</div>
</div>
),
type: types[index],
avatar:
'https://gw.alipayobjects.com/zos/antfincdn/UCSiy1j6jx/xingzhuang.svg',
content: (
<div
style={{
flex: 1,
display: 'flex',
justifyContent: 'flex-end',
}}
>
<div
style={{
width: 200,
}}
>
<div>发布中</div>
<Progress percent={80} />
</div>
</div>
),
}));
const Demo = () => {
const [expandedRowKeys, setExpandedRowKeys] = useState<readonly Key[]>([]);
const [selectedRowKeys, setSelectedRowKeys] = useState<Key[]>([]);
const rowSelection = {
selectedRowKeys,
onChange: (keys: Key[]) => setSelectedRowKeys(keys),
};
const [dataSource, setDataSource] = useState<any[]>([...data] as any[]);
return (
<>
<ProList<{
title: string;
subTitle: React.JSX.Element;
actions: React.JSX.Element[];
description: React.JSX.Element;
type?: 'top' | 'inline' | 'new';
avatar: string;
children: React.JSX.Element;
}>
columns={[
{ dataIndex: 'title', listSlot: 'title' },
{ dataIndex: 'subTitle', listSlot: 'subTitle' },
{ dataIndex: 'type', listSlot: 'type' },
{ dataIndex: 'description', listSlot: 'description' },
{ dataIndex: 'avatar', listSlot: 'avatar' },
{ dataIndex: 'content', listSlot: 'content' },
{ dataIndex: 'actions', listSlot: 'actions' },
]}
toolBarRender={() => [
<Button
key="3"
type="primary"
onClick={() => {
setDataSource([...data.map((item) => ({ ...item }))]);
setTimeout(() => {
const list = [...data.map((item) => ({ ...item }))];
list[1].type = 'new';
setDataSource(list);
}, 0);
}}
>
刷新
</Button>,
]}
rowKey="id"
headerTitle="预设的列状态"
rowSelection={rowSelection}
dataSource={dataSource}
expandable={{
expandedRowKeys,
onExpandedRowsChange: setExpandedRowKeys,
}}
/>
</>
);
};
export default () => (
<div style={{ padding: 24 }}>
<Demo />
</div>
);
#Pagination
import { EllipsisOutlined } from '@ant-design/icons';
import { ProList } from '@ant-design/pro-components';
import { Progress, Tag } from 'antd';
const data = [
'智慧零售平台',
'Ant Design Pro',
'云原生微服务框架',
'数据可视化引擎',
'智能客服系统',
'DevOps 工具链',
'统一权限管理中心',
].map((item) => ({
title: item,
subTitle: <Tag color="#5BD8A6">技术专栏</Tag>,
actions: [
<a key="invite">邀请</a>,
<a key="operate">操作</a>,
<a key="rest">
<EllipsisOutlined />
</a>,
],
avatar:
'https://gw.alipayobjects.com/zos/antfincdn/UCSiy1j6jx/xingzhuang.svg',
content: (
<div
style={{
flex: 1,
display: 'flex',
justifyContent: 'flex-end',
}}
>
<div
style={{
width: 200,
}}
>
<div>发布中</div>
<Progress percent={80} />
</div>
</div>
),
}));
const Demo = () => {
return (
<ProList<any>
pagination={{
defaultPageSize: 5,
showSizeChanger: true,
}}
columns={[
{ dataIndex: 'title', listSlot: 'title' },
{ dataIndex: 'subTitle', listSlot: 'subTitle' },
{ dataIndex: 'type', listSlot: 'type' },
{ dataIndex: 'avatar', listSlot: 'avatar' },
{ dataIndex: 'content', listSlot: 'content' },
{ dataIndex: 'actions', listSlot: 'actions' },
]}
headerTitle="翻页"
dataSource={data}
/>
);
};
export default () => (
<div style={{ padding: 24 }}>
<Demo />
</div>
);
#Custom list item (itemRender)
Use itemRender to customize each item's rendering. The third argument defaultDom is the default list item element (React.ReactElement); you can wrap it or replace it.
import { ProList } from '@ant-design/pro-components';
import { Avatar, theme } from 'antd';
const APPLICATIONS = [
{
id: '1',
title: '帆软 SSO',
avatar:
'https://gw.alipayobjects.com/zos/antfincdn/UCSiy1j6jx/xingzhuang.svg',
description:
'FineReport 是一款用于报表制作、分析和展示的软件,支持数据填报与可视化大屏。',
},
{
id: '2',
title: '泛微 E9 SSO',
avatar:
'https://gw.alipayobjects.com/zos/antfincdn/UCSiy1j6jx/xingzhuang.svg',
description:
'泛微 E9 是一款成熟的协同办公软件,以流程管理、知识管理为核心。',
},
{
id: '3',
title: '销售易',
avatar:
'https://gw.alipayobjects.com/zos/antfincdn/UCSiy1j6jx/xingzhuang.svg',
description:
'SAML (Security Assertion Markup Language) 是一种用于身份认证与授权的标准协议。',
},
];
type AppItem = (typeof APPLICATIONS)[0];
export default () => {
const { token } = theme.useToken();
return (
<div
style={{
backgroundColor: token.colorBgLayout,
margin: -24,
padding: 64,
}}
>
<ProList<AppItem>
rowKey="id"
split
grid={{
xs: 1,
sm: 2,
md: 2,
lg: 4,
xl: 4,
xxl: 4,
gutter: [16, 16],
}}
pagination={false}
columns={[
{ dataIndex: 'title', listSlot: 'title' },
{ dataIndex: 'avatar', listSlot: 'avatar' },
{ dataIndex: 'description', listSlot: 'description' },
]}
dataSource={APPLICATIONS}
toolbar={{
menu: {
type: 'tab',
items: [
{ key: 'all', label: '全部应用' },
{ key: 'dev', label: '开发类' },
{ key: 'ops', label: '运维类' },
{ key: 'office', label: '办公类' },
],
},
}}
itemRender={(item) => (
<div
style={{
width: '100%',
minWidth: 0,
boxSizing: 'border-box',
borderRadius: 8,
padding: 24,
height: '100%',
cursor: 'pointer',
boxShadow: '0 1px 2px rgba(0,0,0,0.03)',
background: token.colorBgContainer,
}}
tabIndex={0}
>
<div style={{ display: 'flex', alignItems: 'flex-start', gap: 12 }}>
<Avatar
src={item.avatar}
shape="square"
size={48}
style={{ flexShrink: 0 }}
/>
<div style={{ flex: 1, minWidth: 0 }}>
<div
style={{
fontWeight: 600,
fontSize: 16,
marginBottom: 4,
}}
>
{item.title}
</div>
<div
style={{
color: token.colorTextSecondary,
fontSize: 14,
lineHeight: 1.5,
overflow: 'hidden',
textOverflow: 'ellipsis',
display: '-webkit-box',
WebkitLineClamp: 2,
WebkitBoxOrient: 'vertical',
}}
>
{item.description}
</div>
</div>
</div>
</div>
)}
/>
</div>
);
};
#API
ProList is built on top of ProTable and supports two column configuration approaches:
columns+listSlot(Recommended): Shares the samecolumnsarray with ProTable. UselistSlotto specify which slot of the list item each column maps to. The samecolumnscan be used for both ProTable and ProList, making it easy to switch between table and list views.metas(Deprecated): The legacy configuration using object keys to map list item parts. Still functional but migration tocolumns+listSlotis recommended.
For other APIs, refer to ProTable.
#request
request is the most important API of ProList, identical to ProTable's usage. request receives an object that must contain data and success; total is also required for manual pagination. request takes over loading management and re-executes when the search form is submitted or params change.
<ProList<DataType, Params>
params={params}
request={async (params, sort, filter) => {
const msg = await myQuery({
page: params.current,
pageSize: params.pageSize,
});
return {
data: msg.result,
success: true,
total: msg.total,
};
}}
/>#ProList
| Property | Description | Type | Default |
|---|---|---|---|
| columns | Column configuration, same as ProTable. Use listSlot to specify list item slots. See columns Configuration | ProColumns<T>[] | - |
| metas | columns + listSlot | Metas | - |
| dataSource | Same configuration as antd. ProList recommends using request to load data | T[] | - |
| request | Method to get dataSource | (params: U & { pageSize?: number; current?: number; keyword?: string }, sort: Record<string, SortOrder>, filter: Record<string, FilterValue>) => Promise<{ data: T[]; success?: boolean; total?: number }> | - |
| params | Extra parameters for request query, triggers reload on change | U | - |
| postData | Process data obtained through request | (data: T[]) => T[] | - |
| defaultData | Default data | T[] | - |
| onDataSourceChange | Triggered when data changes | (dataSource: T[]) => void | - |
| actionRef | Table action reference for custom triggering | React.Ref<ActionType | undefined> | - |
| formRef | Get the form instance of the search form | TableFormItem<T>['formRef'] | - |
| rowKey | Row key, usually row id; falls back to item.key then index when unset | keyof T | (item: T) => React.Key | - |
| headerTitle | Title on the top left | ReactNode | - |
| tooltip | Tooltip next to the title | string | LabelTooltipType | - |
| loading | Whether loading | boolean | { spinning?: boolean } | false |
| split | Whether to show a split line | boolean | true |
| variant | List appearance variant | 'outlined' | 'borderless' | 'filled' | 'borderless' |
| showActions | When to show the actions area. 'hover' shows actions when hovering or focusing the list item | 'hover' | 'always' | 'always' |
| showExtra | When to show the extra area (the aside slot). 'hover' shows it only when hovering the list item. Not supported in CardList mode | 'hover' | 'always' | 'always' |
| footer | List footer | ReactNode | - |
| grid | Grid configuration, enables card mode when set | ListGridType | - |
| itemLayout | List item layout direction | 'horizontal' | 'vertical' | 'horizontal' |
| locale | Internationalization configuration | { emptyText?: ReactNode } | - |
| pagination | Pagination configuration, current and pageSize are managed by request | PaginationConfig | false | false |
| rowSelection | Row selection configuration | TableProps<T>['rowSelection'] & { alwaysShowAlert?: boolean } | false | false |
| expandable | Same configuration as antd | object | false | - |
| onRow | Same configuration as antd. When grid is set, the list renders in card mode and this event will not fire. Use onItem instead | function(record, index) | - |
| onItem | Same configuration as antd. Clicking on an item in all types will trigger this event | function(record, index) | - |
| itemRender | Custom list item render. defaultDom is the default list item element. Wrap or replace it | (item: T, index: number, defaultDom: React.ReactElement) => ReactNode | - |
| rowClassName | Custom list row class name | string | (item: T, index: number) => string | - |
| itemHeaderRender | Customize the header of each item. Different from itemRender, it preserves multi-select and expand/collapse | ((item: T, index: number, defaultDom: JSX.Element | null) => ReactNode) | false | - |
| itemTitleRender | Customize the title rendering of each item | ((item: T, index: number, defaultDom: JSX.Element | null) => ReactNode) | false | - |
| itemCardProps | Custom card list props, only valid in card mode | CheckCardProps | - |
| toolBarRender | Render toolbar, supports returning an array of dom, automatically adds margin-right | ToolBarProps<T>['toolBarRender'] | false | - |
| search | Whether to show the search form, pass an object for search form configuration | false | SearchConfig | false |
| onLoad | Triggered after data loading is complete, may fire multiple times | (dataSource: T[]) => void | - |
| onLoadingChange | Triggered when loading changes, usually caused by network requests | (loading: boolean | SpinProps | undefined) => void | - |
| onRequestError | Triggered when data loading fails | (error: Error) => void | - |
| onSubmit | Triggered when the form is submitted | (params: U) => void | - |
| onReset | Triggered when the form is reset | () => void | - |
| cardProps | Card settings for the outer wrapper | ProCardProps | false | - |
| editable | Editable list configuration, see Editable Table | RowEditableConfig<T> | - |
| manualRequest | Whether to manually trigger the first request | boolean | false |
| ghost | Ghost mode, removes padding from the area | boolean | false |
| dateFormatter | Convert dayjs format data to a specific type, false to skip conversion | "string" | "number" | ((value: dayjs.Dayjs, valueType: string) => string | number) | false | "string" |
| ErrorBoundary | Built-in error handling to prevent blank screens, ErrorBoundary=false disables it | React.ComponentClass<any, any> | false | Built-in ErrorBoundary |
#ActionRef
Sometimes you need to manually trigger ProList's reload and other operations. Use actionRef, which works identically to ProTable ActionRef.
| Method | Description | Type |
|---|---|---|
| reload | Reload the list, pass true to reset page index | (resetPageIndex?: boolean) => Promise<void> |
| reloadAndRest | Reload and clear, page index is also reset, excluding the form | () => Promise<void> |
| reset | Reset to default values, including the form | () => void |
| clearSelected | Clear selected items | () => void |
| startEditable | Start editing a row | (rowKey: Key) => boolean |
| cancelEditable | Cancel editing a row | (rowKey: Key) => boolean |
const ref = useRef<ActionType>();
<ProList actionRef={ref} />;
// Reload
ref.current?.reload();
// Reload and clear, page index is also reset, excluding the form
ref.current?.reloadAndRest();
// Reset to default values, including the form
ref.current?.reset();
// Clear selected items
ref.current?.clearSelected();#columns Configuration (Recommended)
The same columns can be used for both ProTable (table view) and ProList (list view). ProTable ignores the listSlot property, while ProList uses it to map data to list item slots. Columns without listSlot are not rendered in list items but still participate in search form generation.
#ProList Extended Column Properties
| Property | Description | Type | Default |
|---|---|---|---|
| listSlot | Specifies which slot of the list item this column maps to | 'title' | 'subTitle' | 'avatar' | 'description' | 'content' | 'actions' | 'aside' | 'type' | - |
All other column properties are identical to ProTable's ProColumns (dataIndex, valueType, render, search, valueEnum, etc.). See ProTable columns.
#listSlot Slot Reference
| Slot Value | Description | Default valueType |
|---|---|---|
title | List item title | text |
subTitle | List item subtitle | text |
avatar | List item avatar | avatar |
description | List item description | textarea |
content | List item content area | text |
actions | List item action area | option |
aside | Supplementary side content (e.g. images, progress bars, non-interactive content) | text |
type | List item type ('new' | 'top' | 'inline') | text |
#Usage Example
import type { ProColumns } from '@ant-design/pro-components';
// Same columns for both ProTable and ProList
const columns: ProColumns<DataItem>[] = [
{ title: 'Name', dataIndex: 'name', listSlot: 'title' },
{ dataIndex: 'avatar', listSlot: 'avatar', search: false },
{ dataIndex: 'desc', listSlot: 'description', search: false },
{
title: 'Labels',
dataIndex: 'labels',
listSlot: 'subTitle',
search: false,
render: (_, row) => <Tag>{row.label}</Tag>,
},
{
title: 'Actions',
listSlot: 'actions',
search: false,
render: (_, row) => [<a key="edit">Edit</a>],
},
{
title: 'Status',
dataIndex: 'status',
valueType: 'select',
valueEnum: { open: { text: 'Open' }, closed: { text: 'Closed' } },
// No listSlot — not rendered in list items, search only
},
];
<ProList columns={columns} /> // List view
<ProTable columns={columns} /> // Table view#Metas Configuration (Deprecated)
Deprecated:
metasstill works but migration tocolumns+listSlotis recommended. See the Migration Guide below.
Metas uses object keys to map list item parts (title, avatar, description, etc.) to data fields.
#Metas.[Meta] Generic API
| Property | Description | Type | Default |
|---|---|---|---|
| dataIndex | The path corresponding to the data in the data item, supports querying nested paths through an array | string | string[] | - |
| title | Title | ReactNode | - |
| valueType | Value type, same as ProTable, generates different renderers | valueType | 'text' |
| render | Custom render function | (text: ReactNode, record: T, index: number) => ReactNode | ReactNode[] | - |
| valueEnum | Enum of values, automatically converts the value as a key to display the corresponding content | valueEnum | - |
| search | Configure search for the column, false to hide | boolean | { transform: (value: any) => any } | - |
| editable | Whether editable in editable list | boolean | (text: any, record: T, index: number) => boolean | - |
| fieldProps | Props for the search form, passed to the form item | (form, config) => Record | Record | - |
| formItemProps | Configuration passed to Form.Item | (form, config) => formItemProps | formItemProps | - |
| formItemRender | Render the input component of the search form | (item, { type, defaultRender, formItemProps, fieldProps, ...rest }, form) => ReactNode | - |
| key | React key | React.Key | - |
#Metas.type
The field type corresponding to dataSource is 'new' | 'top' | 'inline'.
| Property | Description | Type | Default |
|---|---|---|---|
| dataIndex | - | - | 'type' |
#Metas.title
| Property | Description | Type | Default |
|---|---|---|---|
| dataIndex | - | - | 'title' |
#Metas.subTitle
| Property | Description | Type | Default |
|---|---|---|---|
| dataIndex | - | - | 'subTitle' |
#Metas.description
| Property | Description | Type | Default |
|---|---|---|---|
| dataIndex | - | - | 'description' |
#Metas.avatar
| Property | Description | Type | Default |
|---|---|---|---|
| dataIndex | - | - | 'avatar' |
#Metas.actions
| Property | Description | Type | Default |
|---|---|---|---|
| dataIndex | - | - | 'actions' |
#Metas.content
| Property | Description | Type | Default |
|---|---|---|---|
| dataIndex | - | - | 'content' |
Only
type,title,subTitle,description,avatar,contentandactionskeys are supported inmetas. TheMetas.extradocumented previously does not exist in the type definitions; for aside content uselistSlot: 'aside'incolumns(mapped internally to the list item'sextraarea).
#Batch operations
Same as ProTable, batch operations require setting rowSelection. See ProTable Batch Operations for details.
| Property | Description | Type | Default |
|---|---|---|---|
| alwaysShowAlert | Always show alert, hidden by default when nothing is selected (built-in property of rowSelection) | boolean | - |
| tableAlertRender | Custom left info area of the batch operation toolbar, false to hide | ({ selectedRowKeys: Key[], selectedRows: T[], onCleanSelected: () => void }) => ReactNode) | false | - |
| tableAlertOptionRender | Custom right options area of the batch operation toolbar, false to hide | ({ selectedRowKeys: Key[], selectedRows: T[], onCleanSelected: () => void }) => ReactNode) | false | - |
#Search Form
ProList generates a search form based on fields with search configured in columns or metas. Usage is identical to ProTable Search Form.
#Migrating from metas to columns
metas is deprecated. Migration to columns + listSlot is straightforward: convert each key-value pair in the metas object to an element in the columns array, where the key becomes the listSlot and the value's properties are spread into the column configuration.
#Migration Reference
| metas syntax | columns syntax |
|---|---|
title: { dataIndex: 'name' } | { dataIndex: 'name', listSlot: 'title' } |
avatar: { dataIndex: 'img' } | { dataIndex: 'img', listSlot: 'avatar' } |
description: { dataIndex: 'desc' } | { dataIndex: 'desc', listSlot: 'description' } |
content: {} | { dataIndex: 'content', listSlot: 'content' } |
extra: { render: ... } | { listSlot: 'aside', render: ... } |
status: { title: 'Status', valueType: 'select', ... } | { title: 'Status', dataIndex: 'status', valueType: 'select', ... } |
#Full Example
Before (metas):
<ProList
metas={{
title: { dataIndex: 'name', title: 'Name' },
avatar: { dataIndex: 'avatar', search: false },
description: { dataIndex: 'desc', search: false },
subTitle: {
dataIndex: 'labels',
render: (_, row) => <Tag>{row.label}</Tag>,
search: false,
},
actions: {
render: (_, row) => [<a key="edit">Edit</a>],
search: false,
},
status: {
title: 'Status',
valueType: 'select',
valueEnum: { open: { text: 'Open' }, closed: { text: 'Closed' } },
},
}}
/>After (columns + listSlot):
<ProList
columns={[
{ title: 'Name', dataIndex: 'name', listSlot: 'title' },
{ dataIndex: 'avatar', listSlot: 'avatar', search: false },
{ dataIndex: 'desc', listSlot: 'description', search: false },
{
dataIndex: 'labels',
listSlot: 'subTitle',
render: (_, row) => <Tag>{row.label}</Tag>,
search: false,
},
{
listSlot: 'actions',
render: (_, row) => [<a key="edit">Edit</a>],
search: false,
},
{
title: 'Status',
dataIndex: 'status',
valueType: 'select',
valueEnum: { open: { text: 'Open' }, closed: { text: 'Closed' } },
},
]}
/>Tip: After migration, the same
columnscan be passed directly toProTablefor a one-click switch between list and table views.


