7.12. 基础数据展示组件 - 每一个角落不可缺少的小型组件
摘要:本章将系统性地学习 Ant Design 中所有核心的数据展示组件。这些组件的用途是将结构化数据显示为清晰、易于理解的界面。我们将遵循由简到繁的学习路径,从用于展示单一信息的基础组件开始,逐步深入到用于组织内容的容器组件,最后掌握处理复杂数据集的高级组件。
7.12.1. Tag: 信息的微观标记
我们数据展示之旅的第一站,是 Tag
(标签) 组件。Tag
是 UI 中最基础、也最常见的信息单元之一,专门用于对信息进行小维度的标记、分类和高亮。它的核心价值在于用极小的视觉空间,提供清晰的上下文信息。
核心应用场景:
- 状态标记:标记文章的“已发布”、“草稿”、“待审核”状态。
- 属性归类:标记商品的“新品”、“热销”、“电子产品”属性。
- 关键词展示:在用户配置、文章详情等页面展示用户的技能标签或文章关键词。
第一步:基础用法 - 颜色、图标与状态
Tag
组件最基础的用法就是展示文本,并通过不同的颜色来传递语义。Ant Design 为我们内置了多套预设颜色,同时也支持完全自定义。
核心属性:
color
: 设置标签的颜色。它可以接受三种类型的值:- 预设状态色:
success
, processing
, error
, warning
, default
,用于表达明确的状态语义。 - 预设调色板色:
magenta
, red
, volcano
, orange
, gold
等,提供了丰富的色彩选择。 - 自定义色:标准的
HEX
或 RGB
值,如 #87d068
。
icon
: 在标签文本前添加一个图标,增强视觉表现力。
文件路径: src/components/demos/BasicTagDemo.tsx
(新建文件)

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63
| import React from 'react'; import { SyncOutlined, CheckCircleOutlined, CloseCircleOutlined, ExclamationCircleOutlined, ClockCircleOutlined, MinusCircleOutlined, } from '@ant-design/icons'; import { Flex, Tag, Divider } from 'antd';
const BasicTagDemo: React.FC = () => ( <div className="p-8 bg-white rounded-lg shadow-lg"> <h3 className="text-xl font-bold mb-4 text-center">基础标签用法</h3>
<Divider orientation="left">预设状态色</Divider> <Flex gap="4px 0" wrap> <Tag icon={<CheckCircleOutlined />} color="success"> 成功 </Tag> <Tag icon={<SyncOutlined spin />} color="processing"> 处理中 </Tag> <Tag icon={<CloseCircleOutlined />} color="error"> 失败 </Tag> <Tag icon={<ExclamationCircleOutlined />} color="warning"> 警告 </Tag> <Tag icon={<ClockCircleOutlined />} color="default"> 等待 </Tag> <Tag icon={<MinusCircleOutlined />} color="default"> 暂停 </Tag> </Flex>
<Divider orientation="left">多彩标签</Divider> <Flex gap="4px 0" wrap> <Tag color="magenta">magenta</Tag> <Tag color="red">red</Tag> <Tag color="volcano">volcano</Tag> <Tag color="orange">orange</Tag> <Tag color="gold">gold</Tag> <Tag color="lime">lime</Tag> <Tag color="green">green</Tag> <Tag color="cyan">cyan</Tag> <Tag color="blue">blue</Tag> <Tag color="geekblue">geekblue</Tag> <Tag color="purple">purple</Tag> </Flex> <Divider orientation="left">自定义颜色</Divider> <Flex gap="4px 0" wrap> <Tag color="#f50">#f50</Tag> <Tag color="#2db7f5">#2db7f5</Tag> <Tag color="#87d068">#87d068</Tag> <Tag color="#108ee9">#108ee9</Tag> </Flex> </div> );
export default BasicTagDemo;
|
在 App.tsx
中使用此 Demo:
文件路径: src/App.tsx
(修改文件)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| import React from 'react'; import { ConfigProvider } from 'antd'; import zhCN from 'antd/locale/zh_CN'; import 'dayjs/locale/zh-cn'; import BasicTagDemo from './components/demos/BasicTagDemo';
const App: React.FC = () => { return ( <ConfigProvider locale={zhCN}> <div className="bg-gray-100 min-h-screen p-8 flex items-center justify-center"> <BasicTagDemo /> </div> </ConfigProvider> ); };
export default App;
|
第二步:动态管理 - 添加与删除标签
在真实业务中,标签列表常常是动态变化的,比如用户给自己添加技能标签,或者为文章增删关键词。这要求我们能够通过 state 来动态管理标签数组。
核心思路:
- 使用
useState
维护一个存储所有标签的数组。 - 使用数组的
map
方法,将 state 中的每个标签渲染成一个可关闭的 Tag
组件。 - 为每个
Tag
的 onClose
事件绑定一个处理函数,该函数从 state 数组中移除对应的标签。 - 提供一个
Input
和 Button
(或类似交互)来向 state 数组中添加新的标签。
文件路径: src/components/demos/DynamicTagDemo.tsx
(新建文件)

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77
| import React, { useEffect, useRef, useState } from 'react'; import { PlusOutlined } from '@ant-design/icons'; import { Flex, Input, Tag, Tooltip } from 'antd'; import type { InputRef } from 'antd';
const DynamicTagDemo: React.FC = () => { const [tags, setTags] = useState(['技术', '设计', '产品']); const [inputVisible, setInputVisible] = useState(false); const [inputValue, setInputValue] = useState(''); const inputRef = useRef<InputRef>(null);
useEffect(() => { if (inputVisible) { inputRef.current?.focus(); } }, [inputVisible]);
const handleClose = (removedTag: string) => { const newTags = tags.filter((tag) => tag !== removedTag); setTags(newTags); };
const showInput = () => { setInputVisible(true); };
const handleInputChange = (e: React.ChangeEvent<HTMLInputElement>) => { setInputValue(e.target.value); };
const handleInputConfirm = () => { if (inputValue && !tags.includes(inputValue)) { setTags([...tags, inputValue]); } setInputVisible(false); setInputValue(''); };
return ( <div className="p-8 bg-white rounded-lg shadow-lg"> <h3 className="text-xl font-bold mb-4 text-center">动态添加和删除</h3> <Flex gap="4px 0" wrap> {tags.map<React.ReactNode>((tag, index) => ( <Tag key={tag} closable // 设置为可关闭 onClose={() => handleClose(tag)} > {tag} </Tag> ))} {inputVisible ? ( <Input ref={inputRef} type="text" size="small" style={{ width: 78 }} value={inputValue} onChange={handleInputChange} onBlur={handleInputConfirm} onPressEnter={handleInputConfirm} /> ) : ( <Tag style={{ borderStyle: 'dashed' }} icon={<PlusOutlined />} onClick={showInput} > 新标签 </Tag> )} </Flex> </div> ); };
export default DynamicTagDemo;
|
在 App.tsx
中切换到新 Demo:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| import React from "react"; import { ConfigProvider } from "antd"; import zhCN from "antd/locale/zh_CN"; import "dayjs/locale/zh-cn"; import DynamicTagDemo from "./components/demos/DynamicTagDemo"; const App: React.FC = () => { return ( <ConfigProvider locale={zhCN}> <div className="bg-gray-100 min-h-screen p-8 flex items-center justify-center"> <DynamicTagDemo /> </div> </ConfigProvider> ); };
export default App;
|
第三步:特殊形态 - 可选择标签
有时,我们需要一组外观像 Tag
,但行为像 Checkbox
或 Radio
的组件,用于筛选或分类选择。为此,antd 提供了一个特殊的子组件:Tag.CheckableTag
。
核心要点:
Tag.CheckableTag
是一个 完全受控 的组件。- 它的选中状态由外部传入的
checked
属性决定。 - 当用户点击时,它会触发
onChange
回调,并传入一个新的布尔值(true
或 false
),我们需要在这个回调中更新我们自己的 state。
文件路径: src/components/demos/CheckableTagDemo.tsx
(新建文件)

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39
| import React, { useState } from 'react'; import { Tag } from 'antd';
const { CheckableTag } = Tag;
const categories = ['技术', '人文', '历史', '设计', '产品', '运营'];
const CheckableTagDemo: React.FC = () => { const [selectedTags, setSelectedTags] = useState<string[]>(['技术', '设计']);
const handleChange = (tag: string, checked: boolean) => { const nextSelectedTags = checked ? [...selectedTags, tag] : selectedTags.filter((t) => t !== tag); setSelectedTags(nextSelectedTags); };
return ( <div className="p-8 bg-white rounded-lg shadow-lg"> <h3 className="text-xl font-bold mb-4 text-center">可选择标签</h3> <span style={{ marginRight: 8 }}>分类:</span> {categories.map<React.ReactNode>((tag) => ( <CheckableTag key={tag} checked={selectedTags.includes(tag)} onChange={(checked) => handleChange(tag, checked)} > {tag} </CheckableTag> ))} <p className="mt-4"> <span className="font-semibold">已选分类: </span> {selectedTags.join(', ')} </p> </div> ); };
export default CheckableTagDemo;
|
在 App.tsx
中切换到新 Demo:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| import CheckableTagDemo from './components/demos/CheckableTagDemo';
const App: React.FC = () => { return ( <ConfigProvider locale={zhCN}> <div className="bg-gray-100 min-h-screen p-8 flex items-center justify-center"> {/* <DynamicTagDemo /> */} <CheckableTagDemo /> </div> </ConfigProvider> ); };
|
通过这三个层层递进的示例,我们已经完全掌握了 Tag
组件从静态展示到动态管理,再到特殊选择形态的全部核心用法。虽然它只是一个小组件,但却是构建精细化、高信息密度界面的关键一环。
7.12.2. Badge: 角标信息的聚合与传达
学习了 Tag
之后,我们来掌握它的“近亲”——Badge
(徽标数)。如果说 Tag
是一个独立的信息标签,那么 Badge
则更像是一个 附属的、用于提醒的角标。它通常依附于另一个元素的角落,以一种不打扰主内容的方式,提供关键的聚合信息或状态提示。
核心应用场景:
- 消息通知:在“小铃铛”图标右上角显示未读消息数量。
- 购物应用:在购物车图标上显示已添加的商品数量。
- 状态提醒:在用户头像上显示一个绿点表示“在线”,或在待办事项上显示一个红点表示“有更新”。
第一步:基础用法 - 数字与红点
Badge
最核心的功能,就是作为一个 包装组件,为它的子元素(children
)添加角标。其最常见的形态是展示一个具体的数字,或一个简单的“小红点”。
核心属性:
count
: 需要展示的数字或内容。overflowCount
: 封顶数值。当 count
超过这个值时,会自动显示为 99+
(默认)。dot
: 只展示一个红点,不展示具体数字。适用于仅需提示“有更新”而无需关心具体数量的场景。dot
的优先级高于 count
。showZero
: 当 count
为 0
时,默认不显示徽标。设置此属性为 true
可以强制显示 0
。
文件路径: src/components/demos/BasicBadgeDemo.tsx
(新建文件)

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37
| import React from 'react'; import { Avatar, Badge, Space } from 'antd'; import { BellOutlined, ShoppingCartOutlined } from '@ant-design/icons';
const BasicBadgeDemo: React.FC = () => ( <div className="p-8 bg-white rounded-lg shadow-lg"> <h3 className="text-xl font-bold mb-4 text-center">基础徽标用法</h3> <Space size="large"> {/* 1. 基础用法:为 BellOutlined 图标添加数字角标 */} <Badge count={5}> <Avatar shape="square" size="large" icon={<BellOutlined />} /> </Badge>
{/* 2. 封顶数字:count 为 100,超过默认的 overflowCount={99} */} <Badge count={100}> <Avatar shape="square" size="large" icon={<ShoppingCartOutlined />} /> </Badge>
{/* 3. 自定义封顶数字 */} <Badge count={1000} overflowCount={999}> <Avatar shape="square" size="large" icon={<ShoppingCartOutlined />} /> </Badge>
{/* 4. 小红点:不关心具体数量,只提示有更新 */} <Badge dot> <Avatar shape="square" size="large" icon={<BellOutlined />} /> </Badge> {/* 5. 强制显示0 */} <Badge count={0} showZero> <Avatar shape="square" size="large" /> </Badge> </Space> </div> );
export default BasicBadgeDemo;
|
在 App.tsx
中使用此 Demo:
文件路径: src/App.tsx
(修改文件)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| import React from 'react'; import { ConfigProvider } from 'antd'; import zhCN from 'antd/locale/zh_CN'; import 'dayjs/locale/zh-cn'; import BasicBadgeDemo from './components/demos/BasicBadgeDemo';
const App: React.FC = () => { return ( <ConfigProvider locale={zhCN}> <div className="bg-gray-100 min-h-screen p-8 flex items-center justify-center"> <BasicBadgeDemo /> </div> </ConfigProvider> ); };
export default App;
|
第二步:独立使用与状态点
除了作为角标,Badge
还可以 独立使用,作为一个带有状态色彩和文本的指示器。这在展示任务状态、服务器状态等场景中非常实用。
核心属性:
status
: 设置徽标为状态点模式。可选值为 success
, processing
, default
, error
, warning
。text
: 配合 status 使用,在状态点旁显示的文本。color
: 当预设的状态色不满足需求时,用于自定义状态点的颜色。
文件路径: src/components/demos/StatusBadgeDemo.tsx
(新建文件)

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| import React from 'react'; import { Badge, Space } from 'antd';
const StatusBadgeDemo: React.FC = () => ( <div className="p-8 bg-white rounded-lg shadow-lg"> <h3 className="text-xl font-bold mb-4 text-center">状态点</h3> <Space direction="vertical" size="middle"> <Badge status="success" text="成功" /> <Badge status="error" text="失败" /> <Badge status="default" text="默认" /> <Badge status="processing" text="处理中" /> <Badge status="warning" text="警告" /> <Badge color="magenta" text="自定义颜色" /> </Space> </div> );
export default StatusBadgeDemo;
|
在 App.tsx
中切换到新 Demo:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| import { ConfigProvider } from "antd"; import StatusBadgeDemo from "./components/demos/StatusBadgeDemo"; import zhCN from "antd/locale/zh_CN";
const App: React.FC = () => { return ( <ConfigProvider locale={zhCN}> <div className="bg-gray-100 min-h-screen p-8 flex items-center justify-center"> <StatusBadgeDemo /> </div> </ConfigProvider> ); };
export default App;
|
status
属性为 processing
时,徽标会呈现一个非常醒目的“呼吸”动画,非常适合用于提醒用户有后台任务正在进行中。
第三步:特殊形态 - 缎带 (Badge.Ribbon
)
Badge.Ribbon
是 Badge
的一个特殊子组件,它以“缎带”的形式包裹一个容器(例如 Card
),用于标记整个区块的属性,例如“新品”、“推荐”、“折扣”等。
核心属性:
text
: 显示在缎带上的文本。color
: 自定义缎带的颜色。placement
: 缎带的位置,可选值为 start
或 end
(默认为 end
)。
文件路径: src/components/demos/RibbonBadgeDemo.tsx
(新建文件)

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| import React from 'react'; import { Badge, Card, Space } from 'antd';
const RibbonBadgeDemo: React.FC = () => ( <div className="p-8 bg-white rounded-lg shadow-lg w-[400px]"> <h3 className="text-xl font-bold mb-4 text-center">缎带徽标</h3> <Space direction="vertical" size="middle" style={{ width: '100%' }}> <Badge.Ribbon text="新品推荐" color="magenta"> <Card title="新款智能手机" size="small"> 搭载最新 A20 仿生芯片,性能卓越。 </Card> </Badge.Ribbon> <Badge.Ribbon text="限时折扣" color="volcano" placement="start"> <Card title="高端机械键盘" size="small"> 原价 ¥999,现价 ¥699。 </Card> </Badge.Ribbon> </Space> </div> );
export default RibbonBadgeDemo;
|
在 App.tsx
中切换到新 Demo:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| import RibbonBadgeDemo from './components/demos/RibbonBadgeDemo';
const App: React.FC = () => { return ( <ConfigProvider locale={zhCN}> <div className="bg-gray-100 min-h-screen p-8 flex items-center justify-center"> {/* <StatusBadgeDemo /> */} <RibbonBadgeDemo /> </div> </ConfigProvider> ); };
|
通过这三个示例,我们掌握了 Badge
组件的三种核心形态:作为数字/红点角标、作为独立的状态指示器,以及作为包裹容器的缎带。灵活运用 Badge
,能够极大地提升界面信息传达的效率和视觉吸引力。
7.12.3. Avatar: 身份与标识的视觉载体
在任何涉及用户的界面中,Avatar
(头像) 都是不可或缺的组件。它为用户提供了一个直观的视觉标识,极大地增强了应用的亲和力和可识别性。Ant Design 的 Avatar
组件功能强大且灵活,支持图片、图标和字符三种核心形态,能够满足绝大多数的用户标识需求。
核心应用场景:
- 用户中心:展示当前登录用户的头像。
- 评论列表:在每条评论前显示评论者的头像。
- 项目成员:在一系列堆叠的头像中,展示所有参与项目的成员。
- 内容发布者:在文章或动态旁,标识作者信息。
第一步:三种核心形态与自定义
Avatar
组件的设计核心是其内容的 多样性。通过不同的属性,我们可以轻松创建三种不同类型的头像,并对其尺寸、形状进行自定义。
核心属性/用法:
src
: 传入一个图片 URL,创建图片头像。icon
: 传入一个 ReactNode (通常是 antd 的 Icon),创建图标头像。常用于匿名用户或系统账户。children
: 传入字符串,创建字符头像。通常用于显示用户姓名的首字母。size
: 设置头像尺寸,可以是 small
, default
, large
,也可以是具体的数值 number
。shape
: 设置头像形状,可选值为 circle
(默认) 或 square
。
文件路径: src/components/demos/BasicAvatarDemo.tsx
(新建文件)

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
| import React from 'react'; import { Avatar, Space, Divider } from 'antd'; import { AntDesignOutlined, UserOutlined } from '@ant-design/icons';
const BasicAvatarDemo: React.FC = () => ( <div className="p-8 bg-white rounded-lg shadow-lg"> <h3 className="text-xl font-bold mb-4 text-center">头像核心形态</h3>
<Divider orientation="left">三种内容类型</Divider> <Space size="middle"> <Avatar size={64} icon={<UserOutlined />} /> <Avatar size={64}>U</Avatar> <Avatar size={64} src="https://api.dicebear.com/7.x/miniavs/svg?seed=1" /> </Space>
<Divider orientation="left">不同尺寸</Divider> <Space size="middle"> <Avatar size="large" icon={<UserOutlined />} /> <Avatar icon={<UserOutlined />} /> <Avatar size="small" icon={<UserOutlined />} /> <Avatar size={24} icon={<UserOutlined />} /> </Space>
<Divider orientation="left">不同形状</Divider> <Space size="middle"> <Avatar shape="square" size={64} icon={<UserOutlined />} /> <Avatar shape="circle" size={64} icon={<UserOutlined />} /> </Space> </div> );
export default BasicAvatarDemo;
|
在 App.tsx
中使用此 Demo:
文件路径: src/App.tsx
(修改文件)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| import React from 'react'; import { ConfigProvider } from 'antd'; import zhCN from 'antd/locale/zh_CN'; import 'dayjs/locale/zh-cn'; import BasicAvatarDemo from './components/demos/BasicAvatarDemo';
const App: React.FC = () => { return ( <ConfigProvider locale={zhCN}> <div className="bg-gray-100 min-h-screen p-8 flex items-center justify-center"> <BasicAvatarDemo /> </div> </ConfigProvider> ); };
export default App;
|
第二步:动态调整与加载失败回退
在真实项目中,我们面临两个常见问题:1) 用户名长度不一,如何让字符头像自动适应? 2) 图片链接可能失效,如何优雅地处理“破图”问题?Ant Design 对此都提供了内置的解决方案。
1. 字符头像的动态调整
正如您所建议的,一个常见的需求是截取用户名的首个字符作为头像。Avatar
组件内置了 字体大小自动调整 的逻辑。当 children
为字符串时,它会自动根据头像的 size
和字符串的长度,计算出最合适的 font-size
,确保字符优雅地居中显示。
2. 图片加载失败的回退机制 (Fallback)
当 src
提供的图片链接加载失败时,Avatar
会自动寻找替代方案进行渲染,避免了显示浏览器默认的“破图”图标。
- 回退优先级: `icon` \> `children`。
- 工作流程:
Avatar
尝试加载 src
中的图片。- 如果加载失败,它会检查是否传入了
icon
属性。如果传入了,则渲染该图标。 - 如果没传入
icon
,它会继续检查是否传入了 children
。如果传入了,则渲染该字符。 - 如果
icon
和 children
都没有,则会渲染一个默认的 UserOutlined
图标。
文件路径: src/components/demos/AdvancedAvatarDemo.tsx
(新建文件)

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67
| import React, { useState } from 'react'; import { Avatar, Slider, Space, Divider, Button } from 'antd'; import { UserOutlined } from '@ant-design/icons';
const AdvancedAvatarDemo: React.FC = () => { const [size, setSize] = useState(64); const [name, setName] = useState('Prorise');
const getInitial = (str: string) => (str ? str[0].toUpperCase() : ''); return ( <div className="p-8 bg-white rounded-lg shadow-lg max-w-lg"> <h3 className="text-xl font-bold mb-4 text-center">动态调整与回退</h3>
<Divider orientation="left">字符头像自动调整</Divider> <Space direction="vertical" style={{ width: '100%' }}> <Space wrap> <Avatar size={size}>{getInitial(name)}</Avatar> <Button onClick={() => setName('Prorise Blog')}>换个长名字</Button> <Button onClick={() => setName('Ant')}>换个短名字</Button> </Space> <Slider value={size} onChange={setSize} min={24} max={128} step={1} /> <span>当前尺寸: {size}px</span> </Space>
<Divider orientation="left">图片加载失败回退</Divider> <Space size="large"> <div> <Avatar size={64} src="https://a.bad.url/not-exist.png" // 无效的图片地址 icon={<UserOutlined />} // 提供了 icon 作为回退 /> <p className="text-center text-xs mt-1">回退到 icon</p> </div> <div> <Avatar size={64} src="https://a.bad.url/not-exist.png" // 无效的图片地址 > P </Avatar> <p className="text-center text-xs mt-1">回退到 children</p> </div> <div> <Avatar size={64} src="https://a.bad.url/not-exist.png" // 无效的图片地址 icon={<UserOutlined />} > P </Avatar> <p className="text-center text-xs mt-1">icon 优先</p> </div> </Space> </div> ); };
export default AdvancedAvatarDemo;
|
在 App.tsx
中切换到新 Demo:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| import AdvancedAvatarDemo from './components/demos/AdvancedAvatarDemo';
const App: React.FC = () => { return ( <ConfigProvider locale={zhCN}> <div className="bg-gray-100 min-h-screen p-8 flex items-center justify-center"> {/* <BasicAvatarDemo /> */} <AdvancedAvatarDemo /> </div> </ConfigProvider> ); };
|
第三步:组合形态 - 头像组 (Avatar.Group
)
当需要展示多个相关用户(如项目成员、群组成员)时,将多个头像堆叠在一起是一种非常节省空间且美观的模式。Avatar.Group
子组件就是为此而生的。
核心属性:
max.count
: 设置最多显示的头像数量。超出该数量的头像将被收起到一个 +N
的徽标中。max.style
: 用于自定义 +N
徽标的样式。max.popover
: 用于配置 `+N` 徽标的 Popover 行为,方便在悬浮时展示所有被折叠的用户。
文件路径: src/components/demos/GroupAvatarDemo.tsx
(新建文件)

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36
| import React from 'react'; import { Avatar, Tooltip, Divider } from 'antd'; import { UserOutlined } from '@ant-design/icons';
const GroupAvatarDemo: React.FC = () => ( <div className="p-8 bg-white rounded-lg shadow-lg"> <h3 className="text-xl font-bold mb-4 text-center">头像组</h3> <Divider orientation="left">基础头像组</Divider> <Avatar.Group> <Avatar src="https://api.dicebear.com/7.x/miniavs/svg?seed=1" /> <Avatar style={{ backgroundColor: '#f56a00' }}>K</Avatar> <Tooltip title="Ant User" placement="top"> <Avatar style={{ backgroundColor: '#87d068' }} icon={<UserOutlined />} /> </Tooltip> <Avatar style={{ backgroundColor: '#1677ff' }} icon={<UserOutlined />} /> </Avatar.Group>
<Divider orientation="left">限制数量与 Popover</Divider> <Avatar.Group max={{ count: 2, popover: { title: '所有成员', trigger: 'hover' }, }} > <Avatar src="https://api.dicebear.com/7.x/miniavs/svg?seed=1" /> <Avatar style={{ backgroundColor: '#f56a00' }}>K</Avatar> <Tooltip title="Ant User" placement="top"> <Avatar style={{ backgroundColor: '#87d068' }} icon={<UserOutlined />} /> </Tooltip> <Avatar style={{ backgroundColor: '#1677ff' }} icon={<UserOutlined />} /> </Avatar.Group> </div> );
export default GroupAvatarDemo;
|
在 App.tsx
中切换到新 Demo:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| import GroupAvatarDemo from './components/demos/GroupAvatarDemo';
const App: React.FC = () => { return ( <ConfigProvider locale={zhCN}> <div className="bg-gray-100 min-h-screen p-8 flex items-center justify-center"> {/* <AdvancedAvatarDemo /> */} <GroupAvatarDemo /> </div> </ConfigProvider> ); };
|
通过 Avatar.Group
,我们可以轻松实现优雅的成员列表展示,并通过 max
属性的精细化配置,在简洁性与完整性之间取得完美平衡。掌握 Avatar
的这些用法,将为您的应用增添更多人性化的细节。
7.12.4. QRCode: 信息的编码与分享
在移动互联网时代,QRCode
(二维码) 已成为连接线上与线下、桌面端与移动端不可或缺的桥梁。Ant Design 的 QRCode
组件提供了一个极其简单的方式,在您的 React 应用中动态生成二维码,将任意文本信息(通常是 URL)编码成可被移动设备快速扫描的图形。
核心应用场景:
- 扫码登录:在 PC 网站上显示二维码,用户使用手机 App 扫码后即可授权登录。
- App 下载:引导用户扫描二维码,直接跳转到应用商店的下载页面。
- 内容分享:将当前页面的链接生成二维码,方便用户分享给他人。
- 支付收款:展示收款二维码,简化支付流程。
第一步:基础用法与动态生成
生成一个二维码最少只需要一个属性:value
。这是一个完全由数据驱动的组件,value
属性的任何变化都会立刻反映为二维码图形的更新。
核心属性:
value
: 需要被编码的字符串。这是 QRCode
组件唯一必需的属性。
为了让体验更直观,我们将创建一个带有输入框的示例,让您可以实时修改 value
并观察二维码的变化。
文件路径: src/components/demos/BasicQRCodeDemo.tsx
(新建文件)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| import React, { useState } from 'react'; import { QRCode, Input, Space } from 'antd';
const BasicQRCodeDemo: React.FC = () => { const [text, setText] = useState('https://ant.design/');
return ( <div className="p-8 bg-white rounded-lg shadow-lg w-[400px]"> <h3 className="text-xl font-bold mb-4 text-center">基础二维码</h3> <Space direction="vertical" align="center" style={{ width: '100%' }}> {/* 1. QRCode 组件接收 text state 作为 value */} <QRCode value={text || '-'} /> {/* 2. Input 输入框用于动态修改 text state */} <Input placeholder="请输入链接或文本" maxLength={150} value={text} onChange={(e) => setText(e.target.value)} /> </Space> </div> ); };
export default BasicQRCodeDemo;
|
在 App.tsx
中使用此 Demo:
文件路径: src/App.tsx
(修改文件)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| import React from 'react'; import { ConfigProvider } from 'antd'; import zhCN from 'antd/locale/zh_CN'; import 'dayjs/locale/zh-cn'; import BasicQRCodeDemo from './components/demos/BasicQRCodeDemo';
const App: React.FC = () => { return ( <ConfigProvider locale={zhCN}> <div className="bg-gray-100 min-h-screen p-8 flex items-center justify-center"> <BasicQRCodeDemo /> </div> </ConfigProvider> ); };
export default App;
|
第二步:自定义外观 - 尺寸、颜色与图标
为了让二维码更好地融入您的产品设计,QRCode
组件提供了丰富的外观自定义选项。
核心属性:
size
: 设置二维码的尺寸,单位为 px
,默认为 160。color
: 设置二维码前景色(深色部分)。bgColor
: 设置二维码背景色(浅色部分)。icon
: 在二维码中心嵌入一个 Logo 或图标,通常为图片 URL。iconSize
: 控制中心 icon
的尺寸。
可用性提示:在自定义 color
和 bgColor
时,请务必保持足够的颜色对比度,否则可能导致二维码难以被扫描识别。
文件路径: src/components/demos/CustomQRCodeDemo.tsx
(新建文件)

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
| import React from 'react'; import { QRCode, Space, Divider } from 'antd';
const CustomQRCodeDemo: React.FC = () => ( <div className="p-8 bg-white rounded-lg shadow-lg"> <h3 className="text-xl font-bold mb-4 text-center">自定义外观</h3> <Space size="large" wrap> <div> <p className="text-center mb-2">自定义尺寸</p> <QRCode value="https://ant.design/" size={100} /> </div> <div> <p className="text-center mb-2">自定义颜色</p> <QRCode value="https://ant.design/" color="#1677ff" bgColor="#e6f4ff" /> </div> <div> <p className="text-center mb-2">嵌入图标</p> <QRCode value="https://ant.design/" icon="https://gw.alipayobjects.com/zos/rmsportal/KDpgvguMpGfqaHPjicRK.svg" /> </div> </Space> </div> );
export default CustomQRCodeDemo;
|
在 App.tsx
中切换到新 Demo:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| import CustomQRCodeDemo from './components/demos/CustomQRCodeDemo';
const App: React.FC = () => { return ( <ConfigProvider locale={zhCN}> <div className="bg-gray-100 min-h-screen p-8 flex items-center justify-center"> {/* <BasicQRCodeDemo /> */} <CustomQRCodeDemo /> </div> </ConfigProvider> ); };
|
第三步:状态与纠错等级
QRCode
不仅是一个静态的图形,它还可以拥有自己的“状态”,例如“已过期”、“加载中”。同时,我们还可以设置其“纠错等级”,这在嵌入图标时尤为重要。
核心属性:
status
: 设置二维码的当前状态。可选值为 active
(默认), expired
(已过期), loading
(加载中), scanned
(已扫描)。组件会根据不同状态渲染不同的遮罩层。onRefresh
: 当 status
为 expired
时,用户点击“点击刷新”的回调函数。errorLevel
: 设置二维码的纠错等级。可选值为 'L'
, 'M'
, 'Q'
, 'H'
,纠错能力依次增强。
纠错等级 (Error Level):
二维码的一部分被遮挡后仍能被正常扫描的能力。等级越高,可被遮挡的面积越大,但二维码的像素点也会越密集。
- L: ~7%
- M: ~15% (默认)
- Q: ~25%
- H: ~30%
最佳实践:当您使用 icon
属性在二维码中嵌入图标时,强烈建议 将 errorLevel
设置为 'H'
。因为图标本身就遮挡了一部分二维码信息,提高纠错等级可以确保二维码在有遮挡的情况下依然保持高识别率。
文件路径: src/components/demos/AdvancedQRCodeDemo.tsx
(新建文件)

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53
| import React, { useState } from 'react'; import { QRCode, Space, Button, message, Divider } from 'antd';
const AdvancedQRCodeDemo: React.FC = () => { const [status, setStatus] = useState<"active" | "expired" | "loading" | "scanned">('active');
const handleRefresh = () => { message.success('二维码已刷新!'); setStatus('active'); }
return ( <div className="p-8 bg-white rounded-lg shadow-lg"> <h3 className="text-xl font-bold mb-4 text-center">状态与纠错等级</h3> <Divider orientation="left">状态控制</Divider> <Space direction="vertical" align="center"> <QRCode value="https://ant.design/" status={status} onRefresh={handleRefresh} /> <Button.Group> <Button onClick={() => setStatus('active')}>设为有效</Button> <Button onClick={() => setStatus('scanned')}>设为已扫描</Button> <Button onClick={() => setStatus('expired')}>设为过期</Button> </Button.Group> </Space>
<Divider orientation="left">纠错等级 (嵌入图标时推荐 H)</Divider> <Space size="large"> <div> <p className="text-center mb-2">默认 (M)</p> <QRCode value="https://ant.design/" icon="https://gw.alipayobjects.com/zos/rmsportal/KDpgvguMpGfqaHPjicRK.svg" errorLevel="M" /> </div> <div> <p className="text-center mb-2">高纠错 (H)</p> <QRCode value="https://ant.design/" icon="https://gw.alipayobjects.com/zos/rmsportal/KDpgvguMpGfqaHPjicRK.svg" errorLevel="H" /> </div> </Space> </div> ); };
export default AdvancedQRCodeDemo;
|
在 App.tsx
中切换到新 Demo:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| import AdvancedQRCodeDemo from './components/demos/AdvancedQRCodeDemo';
const App: React.FC = () => { return ( <ConfigProvider locale={zhCN}> <div className="bg-gray-100 min-h-screen p-8 flex items-center justify-center"> {/* <CustomQRCodeDemo /> */} <AdvancedQRCodeDemo /> </div> </ConfigProvider> ); };
|
通过掌握 QRCode
的基础用法、外观定制以及高级的状态和纠错等级配置,您可以轻松地在应用中集成强大而美观的二维码功能,有效打通不同设备和场景间的信息壁垒。
7.12.5. Image: 优雅的图片展示与交互
在现代 Web 应用中,图片是内容传达不可或缺的一部分。然而,原生的 <img>
标签功能有限,无法很好地处理加载失败、大图预览等常见场景。Ant Design 的 Image
组件正是为此而生,它在原生功能的基础上,内置了 加载占位、失败回退、点击预览 以及 相册模式 等一系列强大功能,极大提升了图片展示的用户体验。
核心应用场景:
- 文章内容插图:在文章中嵌入图片,并提供点击查看大图的功能。
- 商品详情页:展示商品的多张图片,并以相册的形式进行预览切换。
- 个人资料页:展示用户上传的个人照片墙。
第一步:基础用法与容错处理
Image
组件的基础用法与 <img>
标签非常相似,但它通过 fallback
属性,提供了一种优雅处理图片加载失败的机制。
核心属性:
src
: 图片的 URL 地址。width
/ height
: 设置图片显示的宽度和高度。fallback
: 当 `src` 加载失败时,用于替代显示的容错图片 URL。placeholder
: 加载占位符。当设置为 true
时,会在图片加载过程中显示一个默认的占位动画。
文件路径: src/components/demos/BasicImageDemo.tsx
(新建文件)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35
| import React from 'react'; import { Image, Space } from 'antd';
const BasicImageDemo: React.FC = () => ( <div className="p-8 bg-white rounded-lg shadow-lg"> <h3 className="text-xl font-bold mb-4 text-center">基础用法与容错</h3> <Space size="large" wrap> <div> <p className="text-center mb-2">基础用法</p> <Image width={200} src="https://zos.alipayobjects.com/rmsportal/jkjgkEfvpUPVyRjUImniVslZfWPnJuuZ.png" /> </div> <div> <p className="text-center mb-2">加载失败回退</p> <Image width={200} src="https://a.bad.url/error.png" // 故意使用一个无效链接 fallback="https://zos.alipayobjects.com/rmsportal/jkjgkEfvpUPVyRjUImniVslZfWPnJuuZ.png?x-oss-process=image/blur,r_50,s_50/quality,q_1/resize,m_mfit,h_200,w_200" /> </div> <div> <p className="text-center mb-2">渐进加载 (Placeholder)</p> <Image width={200} src="https://images.unsplash.com/photo-1593642532454-e138e28a63f4?ixid=MnwxMjA3fDB8MHxzZWFyY2h8MXx8d29ya3xlbnwwfHwwfHw%3D&ixlib=rb-1.2.1&w=1000&q=80" // 一个较大的图片 placeholder /> </div> </Space> </div> );
export default BasicImageDemo;
|
在 App.tsx
中使用此 Demo:
文件路径: src/App.tsx
(修改文件)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| import React from 'react'; import { ConfigProvider } from 'antd'; import zhCN from 'antd/locale/zh_CN'; import 'dayjs/locale/zh-cn'; import BasicImageDemo from './components/demos/BasicImageDemo';
const App: React.FC = () => { return ( <ConfigProvider locale={zhCN}> <div className="bg-gray-100 min-h-screen p-8 flex items-center justify-center"> <BasicImageDemo /> </div> </ConfigProvider> ); };
export default App;
|
第二步:核心功能 - 图片预览与相册模式 (PreviewGroup
)
Image
组件最强大的功能之一就是其内置的预览能力。默认情况下,所有 Image
组件都是可以点击预览的。当我们需要将页面上 一组 相关的图片组织成一个相册,让用户可以在预览模式下左右切换时,就需要使用 Image.PreviewGroup
。
核心用法:
- 将多个
Image
组件包裹在 <Image.PreviewGroup>
标签内。 Image.PreviewGroup
会自动收集所有子 Image
的 src
,形成一个图片数组。- 点击其中任意一张图片,都会以相册模式打开预览,并允许用户在所有图片间导航。
文件路径: src/components/demos/GroupImageDemo.tsx
(新建文件)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| import React from 'react'; import { Image, Space } from 'antd';
const GroupImageDemo: React.FC = () => ( <div className="p-8 bg-white rounded-lg shadow-lg"> <h3 className="text-xl font-bold mb-4 text-center">多图预览 (相册模式)</h3> <Image.PreviewGroup> <Space size="large" wrap> <Image width={200} src="https://gw.alipayobjects.com/zos/rmsportal/KDpgvguMpGfqaHPjicRK.svg" /> <Image width={200} src="https://gw.alipayobjects.com/zos/antfincdn/aPkFc8Sj7n/method-draw-image.svg" /> <Image width={200} src="https://zos.alipayobjects.com/rmsportal/jkjgkEfvpUPVyRjUImniVslZfWPnJuuZ.png" /> </Space> </Image.PreviewGroup> </div> );
export default GroupImageDemo;
|
在 App.tsx
中切换到新 Demo:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| import GroupImageDemo from './components/demos/GroupImageDemo';
const App: React.FC = () => { return ( <ConfigProvider locale={zhCN}> <div className="bg-gray-100 min-h-screen p-8 flex items-center justify-center"> {/* <BasicImageDemo /> */} <GroupImageDemo /> </div> </ConfigProvider> ); };
|
第三步:受控预览与自定义
在某些复杂场景下,我们可能需要通过 编程方式 来控制预览的打开和关闭,而不是依赖用户的点击。例如,点击一个按钮来打开一个图片相册。这可以通过 preview
属性实现受控。
核心属性:
preview
: 预览参数配置,可以是一个对象。preview.visible
: 控制预览模态框的显示与隐藏。preview.onVisibleChange
: 当 visible
状态变化时(用户手动关闭预览)的回调,用于同步我们自己的 state。preview.current
: 在 PreviewGroup
中,用于指定默认打开哪一张图片。
文件路径: src/components/demos/ControlledImageDemo.tsx
(新建文件)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48
| import React, { useState } from "react"; import { Image, Button, Space } from "antd";
const imageList = [ "https://zos.alipayobjects.com/rmsportal/jkjgkEfvpUPVyRjUImniVslZfWPnJuuZ.png", "https://gw.alipayobjects.com/zos/rmsportal/KDpgvguMpGfqaHPjicRK.svg", "https://gw.alipayobjects.com/zos/antfincdn/aPkFc8Sj7n/method-draw-image.svg", ];
const ControlledImageDemo: React.FC = () => { const [visible, setVisible] = useState(false); const [current, setCurrent] = useState(0);
const showPreview = (index: number) => { setCurrent(index); setVisible(true); };
const previewProps = { visible, current, onVisibleChange: (vis: boolean) => setVisible(vis), };
return ( <div className="p-8 bg-white rounded-lg shadow-lg"> <h3 className="text-xl font-bold mb-4 text-center">受控预览</h3> <Space> {imageList.map((imgSrc, index) => ( <Button key={imgSrc} onClick={() => showPreview(index)}> 查看图片 {index + 1} </Button> ))} </Space>
{/* 这是一个隐藏的 PreviewGroup */} <div className="hidden"> <Image.PreviewGroup preview={previewProps}> {imageList.map((imgSrc, index) => ( <Image key={imgSrc} src={imgSrc} /> ))} </Image.PreviewGroup> </div> </div> ); };
export default ControlledImageDemo;
|
在 App.tsx
中切换到新 Demo:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| import ControlledImageDemo from './components/demos/ControlledImageDemo';
const App: React.FC = () => { return ( <ConfigProvider locale={zhCN}> <div className="bg-gray-100 min-h-screen p-8 flex items-center justify-center"> {/* <GroupImageDemo /> */} <ControlledImageDemo /> </div> </ConfigProvider> ); };
|
通过 preview
对象的精细化配置,我们获得了对预览行为的完全控制权。Image
组件的这些高级功能,使其不仅仅是一个图片展示工具,更是一个功能完备的图片交互解决方案。
Tooltip
(文字提示) 是 UI 中用于提供“即时帮助”的最常用组件。当界面上的某个元素功能不够直观(如图标按钮),或者信息无法完全展示时(如被截断的文本),Tooltip
能在用户鼠标悬浮时,提供一个轻量级的、非侵入式的文字气泡,用以补充说明。
核心应用场景:
- 解释图标按钮:为一个删除(垃圾桶)图标按钮提供 “删除此项” 的文字提示。
- 显示完整文本:当表格单元格或标签因空间不足而截断文本时,用
Tooltip
显示完整内容。 - 提供额外信息:为一个禁用的按钮解释其不可用的原因。
Tooltip
vs Popover
Tooltip
: 设计用来承载 简单的、纯文本 的提示。它的结构轻量,交互单一(悬浮显示/移出隐藏)。Popover
: 设计用来承载 更复杂的内容,例如标题、正文、按钮组或表单。它是一个功能更全面的“气泡卡片”。
第一步:基础用法
Tooltip
的本质是一个 包装组件。您只需要将需要触发提示的目标元素(children
)包裹在 <Tooltip>
标签内,并通过 title
属性提供提示的文本内容即可。
核心属性:
title
: 提示的文本内容。这是 Tooltip
组件唯一必需的属性。
文件路径: src/components/demos/BasicTooltipDemo.tsx
(新建文件)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| import React from 'react'; import { Button, Tooltip } from 'antd'; import { DeleteOutlined } from '@ant-design/icons';
const BasicTooltipDemo: React.FC = () => ( <div className="p-8 bg-white rounded-lg shadow-lg"> <h3 className="text-xl font-bold mb-4 text-center">基础用法</h3> <div className="flex justify-center items-center gap-4"> {/* 1. 包裹一个文本元素 */} <Tooltip title="这是一个文字提示"> <span>悬浮在我上方</span> </Tooltip>
{/* 2. 包裹一个按钮,解释其功能 */} <Tooltip title="这是一个普通的按钮"> <Button>按钮</Button> </Tooltip>
{/* 3. 最常见的用法:解释一个图标按钮 */} <Tooltip title="删除"> <Button danger shape="circle" icon={<DeleteOutlined />} /> </Tooltip> </div> </div> );
export default BasicTooltipDemo;
|
在 App.tsx
中使用此 Demo:
文件路径: src/App.tsx
(修改文件)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| import React from 'react'; import { ConfigProvider } from 'antd'; import zhCN from 'antd/locale/zh_CN'; import 'dayjs/locale/zh-cn'; import BasicTooltipDemo from './components/demos/BasicTooltipDemo';
const App: React.FC = () => { return ( <ConfigProvider locale={zhCN}> <div className="bg-gray-100 min-h-screen p-8 flex items-center justify-center"> <BasicTooltipDemo /> </div> </ConfigProvider> ); };
export default App;
|
重要提示:Tooltip
的触发依赖于其子元素的 onMouseEnter
, onMouseLeave
等事件。如果子元素本身被禁用了(例如 <Button disabled />
),它将无法响应这些鼠标事件,从而导致 Tooltip
不会显示。
解决方案:在禁用的元素外再包裹一层 <span>
标签,将 Tooltip
应用于这个 <span>
上。
<Tooltip title="..."><span className="cursor-not-allowed"><Button disabled>...</Button></span></Tooltip>
第二步:位置与自定义样式
为了应对复杂的布局,Tooltip
提供了 12 个 不同的弹出位置。同时,您也可以自定义其背景颜色以更好地融入您的设计系统。
核心属性:
placement
: 设置提示框的弹出位置。例如 top
, left
, bottomRight
等。color
: 自定义提示框的背景颜色。antd 内置了多种预设颜色,也支持传入标准的 HEX
色值。arrow
: 控制是否显示指向目标元素的箭头。
文件路径: src/components/demos/PlacementTooltipDemo.tsx
(新建文件)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56
| import React from 'react'; import { Button, Tooltip, Divider, Space, Row, Col } from 'antd';
const BasicTooltipDemo: React.FC = () => ( <div className="p-8 bg-white rounded-lg shadow-lg"> <h3 className="text-xl font-bold mb-4 text-center">位置与颜色</h3> <Divider orientation="left">4 个弹出位置</Divider> <div className="w-[400px]"> <Row justify="center" style={{ marginBottom: 8 }}> <Col> <Tooltip placement="top" title="顶部提示"> <Button>Top</Button> </Tooltip> </Col> </Row> <Row justify="space-between" style={{ marginBottom: 8 }}> <Col> <Tooltip placement="left" title="左侧提示"> <Button>Left</Button> </Tooltip> </Col> <Col> <Tooltip placement="right" title="右侧提示"> <Button>Right</Button> </Tooltip> </Col> </Row> <Row justify="center"> <Col> <Tooltip placement="bottom" title="底部提示"> <Button>Bottom</Button> </Tooltip> </Col> </Row> </div>
<Divider orientation="left">多彩提示</Divider> <Space wrap> <Tooltip title="温柔的粉色主题" color="pink"> <Button>粉色</Button> </Tooltip> <Tooltip title="热情的红色主题" color="red"> <Button>红色</Button> </Tooltip> <Tooltip title="活力的橙色主题" color="orange"> <Button>橙色</Button> </Tooltip> <Tooltip title="清新的蓝色主题" color="#108ee9"> <Button>蓝色</Button> </Tooltip> </Space> </div> );
export default BasicTooltipDemo;
|
在 App.tsx
中切换到新 Demo:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| import PlacementTooltipDemo from './components/demos/PlacementTooltipDemo';
const App: React.FC = () => { return ( <ConfigProvider locale={zhCN}> <div className="bg-gray-100 min-h-screen p-8 flex items-center justify-center"> {/* <BasicTooltipDemo /> */} <PlacementTooltipDemo /> </div> </ConfigProvider> ); };
|
通过灵活运用 placement
和 color
属性,您可以让 Tooltip
精准地出现在期望的位置,并与您的整体 UI 风格保持一致。掌握 Tooltip
是提升应用易用性和细节体验的重要一步。
7.12.7. Popover: 承载复杂内容的浮层卡片
Popover
(气泡卡片) 是 Tooltip
的功能扩展。当一个简单的文字提示(Tooltip
)已不足以承载所需信息,而您希望在一个浮层中展示 标题、多行文本、链接、按钮 甚至更复杂的组件时,Popover
便是最佳选择。它同样由用户操作(如悬浮或点击)触发,但提供了一个功能更完备的“卡片”作为容器。
核心应用场景:
- 用户卡片:点击用户头像,弹出一个包含用户名、简介和“关注”按钮的
Popover
。 - 快捷操作:点击表格行末尾的“更多”(
...
)图标,弹出一个包含“编辑”、“删除”等操作链接的 Popover
。 - 产品快速预览:在电商列表中,鼠标悬浮于商品图片上,弹出一个包含商品名称、价格和“加入购物车”按钮的
Popover
。
第一步:基础用法 - 标题与内容
与 Tooltip
类似,Popover
也是一个包装组件。但它提供了两个核心属性来构建卡片式结构:title
用于定义卡片头部,content
用于填充卡片主体。content
属性的强大之处在于它可以接收任意的 ReactNode
。
核心属性:
title
: 设置气泡卡片的标题。content
: 设置气泡卡片的主体内容。这里可以放入文本、链接、按钮等任意 React 元素。
文件路径: src/components/demos/BasicPopoverDemo.tsx
(新建文件)

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
| import React from 'react'; import { Button, Popover } from 'antd';
const BasicPopoverDemo: React.FC = () => { const popoverTitle = <span>个人资料</span>;
const popoverContent = ( <div> <p>姓名:张三</p> <p>职位:前端开发工程师</p> <a href="https://ant.design" target="_blank" rel="noopener noreferrer"> 查看更多 </a> </div> );
return ( <div className="p-8 bg-white rounded-lg shadow-lg"> <h3 className="text-xl font-bold mb-4 text-center">基础用法</h3> <div className="flex justify-center"> <Popover content={popoverContent} title={popoverTitle} trigger="hover" // 默认触发方式是 hover > <Button type="primary">悬浮以显示用户信息</Button> </Popover> </div> </div> ); };
export default BasicPopoverDemo;
|
在 App.tsx
中使用此 Demo:
文件路径: src/App.tsx
(修改文件)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| import React from 'react'; import { ConfigProvider } from 'antd'; import zhCN from 'antd/locale/zh_CN'; import 'dayjs/locale/zh-cn'; import BasicPopoverDemo from './components/demos/BasicPopoverDemo';
const App: React.FC = () => { return ( <ConfigProvider locale={zhCN}> <div className="bg-gray-100 min-h-screen p-8 flex items-center justify-center"> <BasicPopoverDemo /> </div> </Provider> ); };
export default App;
|
第二步:触发方式与受控模式
Popover
提供了多种方式来控制其显示和隐藏,以适应不同的交互需求。
1. 触发方式 (trigger
)
trigger
: 设置触发 Popover 显示的行为。hover
: 鼠标悬浮(默认)。focus
: 元素获得焦点时,通常用于输入框。click
: 鼠标点击时。contextMenu
: 鼠标右键点击时。
2. 受控模式
在某些场景下,我们需要通过编程来精确控制 Popover
的可见性,例如,实现一个可以从浮层内部关闭自身的 Popover
。这需要使用受控模式。
open
: 使用 React State 来控制 Popover 是否可见onOpenChange
: 当 Popover 的可见状态因用户操作(如点击外部)而应发生变化时,触发此回调。我们需要在此回调中更新 open
绑定的 state。
文件路径: src/components/demos/TriggerPopoverDemo.tsx
(新建文件)

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
| import React, { useState } from "react"; import { Button, Popover, Divider, Space, Input } from "antd";
const TriggerPopoverDemo: React.FC = () => { const [open, setOpen] = useState(false);
const hide = () => { setOpen(false); };
const handleOpenChange = (newOpen: boolean) => { setOpen(newOpen); };
return ( <div className="p-8 bg-white rounded-lg shadow-lg"> <h3 className="text-xl font-bold mb-4 text-center">触发方式与受控</h3> <Divider orientation="left">受控模式 (从浮层内关闭)</Divider> <Space> <Popover content={<a onClick={hide}>点我关闭</a>} title="这是一个受控的 Popover" trigger="click" open={open} onOpenChange={handleOpenChange} > <Button type="primary">点击我</Button> </Popover> </Space> </div> ); }; export default TriggerPopoverDemo;
|
在 App.tsx
中切换到新 Demo:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| import { ConfigProvider } from 'antd'; import TriggerPopoverDemo from './components/demos/TriggerPopoverDemo'; import zhCN from 'antd/locale/zh_CN';
const App: React.FC = () => { return ( <ConfigProvider locale={zhCN}> <div className="bg-gray-100 min-h-screen p-8 flex items-center justify-center"> {/* <BasicPopoverDemo /> */} <TriggerPopoverDemo /> </div> </ConfigProvider> ); };
export default App;
|
通过 trigger
属性,我们可以轻松适配不同的交互场景。而受控模式则为我们实现复杂的、需要内部状态与外部状态同步的 Popover
交互提供了可能,是构建高级浮层操作面板的关键。掌握 Popover
,意味着您已经具备了在有限的界面空间内,优雅地组织和呈现丰富信息与操作的能力。
7.12.8. Statistic: 关键数据的强调与展示
Statistic
(统计数值) 是一个专门用于 突出展示关键绩效指标 (KPI) 或特定数值 的组件。与简单地将数字作为文本显示不同,Statistic
提供了一套标准的视觉结构(标题、数值、前/后缀),让数据一目了然,极大地增强了信息的可读性和视觉冲击力。它是各类仪表盘、数据报表和概览页面的核心构成元素。
核心应用场景:
- 仪表盘 KPI:展示“今日活跃用户”、“本月销售额”、“同比增长率”等核心指标。
- 个人中心:显示用户的“账户余额”、“积分”、“等级”等信息。
- 活动页面:以倒计时的形式展示“距活动结束还剩”的时间。
第一步:基础用法与格式化
Statistic
的基础用法非常直观,通过 title
和 value
两个核心属性即可定义一个统计项。更进一步,我们可以通过前缀、后缀、精度控制等属性,对数值进行丰富的格式化。
核心属性:
title
: 数值的标题,说明该数字的含义。value
: 需要展示的核心数值。prefix
: 在数值 前 添加的前缀,可以是文本或图标(如货币符号 $
)。suffix
: 在数值 后 添加的后缀,可以是文本或图标(如单位 元
或百分号 %
)。precision
: 设置数值的小数点精度。valueStyle
: 用于单独设置数值区域的 CSS 样式,常用于根据数值正负改变颜色。
文件路径: src/components/demos/BasicStatisticDemo.tsx
(新建文件)

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55
| import React, { useState } from "react"; import { Statistic, Card, Row, Col, Slider } from "antd"; import { LikeOutlined, } from "@ant-design/icons";
const BasicStatisticDemo: React.FC = () => { const [rating, setRating] = useState(93);
const getRatingColor = (value: number) => { if (value < 40) return "#ff4d4f"; if (value < 70) return "#faad14"; return "#3f8600"; };
return ( <div className="p-8 bg-gray-100 rounded-lg shadow-inner w-[800px]"> <h3 className="text-xl font-bold mb-4 text-center">基础用法与格式化</h3> <Row gutter={16}> <Col span={12}> <Card> <Statistic title="活跃用户 (个)" value={112893} /> </Card> </Col> <Col span={12}> <Card> <Statistic title="账户余额 (元)" value={112893.2} precision={2} /> </Card> </Col> <Col span={12}> <Card> <Statistic title="好评率" value={rating} precision={2} valueStyle={{ color: getRatingColor(rating) }} prefix={<LikeOutlined />} suffix="%" /> <div style={{ marginTop: 16 }}> <Slider min={0} max={100} value={rating} onChange={setRating} /> </div> </Card> </Col> </Row> </div> ); };
export default BasicStatisticDemo;
|
在 App.tsx
中使用此 Demo:
文件路径: src/App.tsx
(修改文件)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| import React from 'react'; import { ConfigProvider } from 'antd'; import zhCN from 'antd/locale/zh_CN'; import 'dayjs/locale/zh-cn'; import BasicStatisticDemo from './components/demos/BasicStatisticDemo';
const App: React.FC = () => { return ( <ConfigProvider locale={zhCN}> <div className="bg-gray-100 min-h-screen p-8 flex items-center justify-center"> <BasicStatisticDemo /> </div> </ConfigProvider> ); };
export default App;
|
第二步:动态数值 - 计时器 (Statistic.Timer
)
除了展示静态数值,Statistic
还提供了一个强大的子组件 Statistic.Timer
,专门用于处理动态变化的计时需求,如倒计时和正计时。
版本提示 (2025 年): 旧版的 Statistic.Countdown
组件在 antd v5.25.0
后已被废弃。请 始终使用 功能更全面、API 更统一的 Statistic.Timer
组件。
核心属性:
type
: 设置计时器类型。'countdown'
表示倒计时,'countup'
表示正计时。value
: 计时器的目标或起始时间戳。通常使用 Date.now()
加上或减去一个时间段来设定。format
: 格式化时间的字符串,遵循 day.js 的格式规范 (如 HH:mm:ss
)。onFinish
: 仅在 type='countdown'
时有效,当倒计时结束时触发的回调函数。
文件路径: src/components/demos/TimerStatisticDemo.tsx
(新建文件)

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47
| import React from 'react'; import { Statistic, Card, Row, Col, message } from 'antd'; import dayjs from 'dayjs';
const { Timer } = Statistic;
const deadline = dayjs().add(30, 'second').valueOf();
const startTime = dayjs().subtract(1, 'hour').valueOf();
const TimerStatisticDemo: React.FC = () => { const onFinish = () => { message.success('倒计时结束!'); };
return ( <div className="p-8 bg-gray-100 rounded-lg shadow-inner w-[800px]"> <h3 className="text-xl font-bold mb-4 text-center">计时器</h3> <Row gutter={16}> <Col span={12}> <Card> <Timer title="活动倒计时" type="countdown" value={deadline} onFinish={onFinish} format="HH:mm:ss" /> </Card> </Col> <Col span={12}> <Card> <Timer title="通话时长" type="countup" value={startTime} format="HH:mm:ss" /> </Card> </Col> </Row> </div> ); };
export default TimerStatisticDemo;
|
在 App.tsx
中切换到新 Demo:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| import TimerStatisticDemo from './components/demos/TimerStatisticDemo';
const App: React.FC = () => { return ( <ConfigProvider locale={zhCN}> <div className="bg-gray-100 min-h-screen p-8 flex items-center justify-center"> {/* <BasicStatisticDemo /> */} <TimerStatisticDemo /> </div> </ConfigProvider> ); };
|
Statistic.Timer
内部封装了 requestAnimationFrame
或 setInterval
等复杂的计时和重渲染逻辑,让我们能够以极低的成本,在界面中实现精准、高效的动态计时功能。掌握 Statistic
组件,是构建数据驱动型应用和信息可视化界面的重要一步。
7.12.9. Empty: 优雅地处理“无”
在任何数据驱动的应用中,“没有数据”是一种常态,而非例外。Empty
(空状态) 组件的使命,就是将这种原本冰冷、空白的界面,转化为一个友好的、信息明确的、甚至能引导用户进行下一步操作的交互节点。它避免了用户在看到空白页面时产生“是出错了还是真的没有?”的困惑。
核心应用场景:
- 列表为空:当表格、列表或卡片组在当前筛选条件下没有数据时。
- 搜索无结果:当用户执行搜索后,没有找到任何匹配项。
- 初始化引导:在一个新功能或新项目中,没有任何内容时,引导用户“创建第一个项目”。
无处不在的 Empty
:Ant Design 的美妙之处在于,Empty
组件已经 内置 到了所有可能出现空状态的组件中,例如 Table
, List
, Select
, Cascader
等。当这些组件的 dataSource
为空时,它们会自动渲染一个默认的 Empty
状态。因此,我们单独学习它,更多是为了在自定义布局或全局替换默认样式时使用。
第一步:基础用法与不同样式
Empty
组件最基础的用法就是直接渲染它,它会显示一个默认的插图和“暂无数据”的描述。Ant Design 还提供了一个更简洁的 simple
样式,适用于空间有限的场景。
核心属性:
image
: 设置显示的图片。可以直接传入 antd 内置的 Empty.PRESENTED_IMAGE_SIMPLE
,也可以传入一个图片 URL 字符串。description
: 自定义图片下方的描述文本。传入 false
可以隐藏描述。
文件路径: src/components/demos/BasicEmptyDemo.tsx
(新建文件)

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| import React from 'react'; import { Empty, Divider } from 'antd';
const BasicEmptyDemo: React.FC = () => ( <div className="p-8 bg-white rounded-lg shadow-lg"> <h3 className="text-xl font-bold mb-4 text-center">基础用法与内置样式</h3> <Divider orientation="left">默认样式</Divider> <Empty />
<Divider orientation="left">简洁样式 (Simple)</Divider> <Empty image={Empty.PRESENTED_IMAGE_SIMPLE} />
<Divider orientation="left">无描述</Divider> <Empty description={false} /> </div> );
export default BasicEmptyDemo;
|
在 App.tsx
中使用此 Demo:
文件路径: src/App.tsx
(修改文件)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| import React from 'react'; import { ConfigProvider } from 'antd'; import zhCN from 'antd/locale/zh_CN'; import 'dayjs/locale/zh-cn'; import BasicEmptyDemo from './components/demos/BasicEmptyDemo';
const App: React.FC = () => { return ( <ConfigProvider locale={zhCN}> <div className="bg-gray-100 min-h-screen p-8 flex items-center justify-center"> <BasicEmptyDemo /> </div> </ConfigProvider> ); };
export default App;
|
第二步:完全自定义与操作引导
Empty
组件最强大的地方在于它的 高度可定制性。我们可以替换它的图片、文字,甚至可以在其下方添加 操作按钮,将一个消极的“空状态”转变为一个积极的“操作起点”。
核心用法:
image
: 传入一个完整的 <img>
标签或任何 ReactNode
来作为自定义图片。children
: 在 Empty
组件的标签内部添加的任何子元素,都会被渲染在描述文字的下方。这通常是放置“创建”或“刷新”按钮的最佳位置。
文件路径: src/components/demos/CustomizeEmptyDemo.tsx
(新建文件)

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| import React from 'react'; import { Empty, Button } from 'antd';
const CustomizeEmptyDemo: React.FC = () => ( <div className="p-8 bg-white rounded-lg shadow-lg"> <h3 className="text-xl font-bold mb-4 text-center">自定义与操作引导</h3> <Empty image="https://gw.alipayobjects.com/zos/antfincdn/ZHrcdLPrvN/empty.svg" imageStyle={{ height: 60 }} description={ <span> 暂无项目,快来 <a href="#API">创建一个</a> 吧! </span> } > <Button type="primary">立即创建</Button> </Empty> </div> );
export default CustomizeEmptyDemo;
|
在 App.tsx
中切换到新 Demo:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| import CustomizeEmptyDemo from './components/demos/CustomizeEmptyDemo';
const App: React.FC = () => { return ( <ConfigProvider locale={zhCN}> <div className="bg-gray-100 min-h-screen p-8 flex items-center justify-center"> {/* <BasicEmptyDemo /> */} <CustomizeEmptyDemo /> </div> </ConfigProvider> ); };
|
通过自定义 image
、description
和 children
,我们可以创造出完全符合产品调性和业务逻辑的空状态,将原本可能导致用户流失的空白页面,变成提升用户体验和转化率的有效触点。
至此,我们已经完成了第一阶段所有基础数据展示组件的学习。接下来,我们将进入 阶段二:内容容器组件,学习如何将这些基础组件有效地组织起来。