第七章 第四节:Ant Design 基础数据展示组件篇 —— 拆解小型数据组件设计逻辑,掌握 “角落级” 组件

7.12. 基础数据展示组件 - 每一个角落不可缺少的小型组件

摘要:本章将系统性地学习 Ant Design 中所有核心的数据展示组件。这些组件的用途是将结构化数据显示为清晰、易于理解的界面。我们将遵循由简到繁的学习路径,从用于展示单一信息的基础组件开始,逐步深入到用于组织内容的容器组件,最后掌握处理复杂数据集的高级组件。

7.12.1. Tag: 信息的微观标记

我们数据展示之旅的第一站,是 Tag (标签) 组件。Tag 是 UI 中最基础、也最常见的信息单元之一,专门用于对信息进行小维度的标记、分类和高亮。它的核心价值在于用极小的视觉空间,提供清晰的上下文信息。

核心应用场景:

  • 状态标记:标记文章的“已发布”、“草稿”、“待审核”状态。
  • 属性归类:标记商品的“新品”、“热销”、“电子产品”属性。
  • 关键词展示:在用户配置、文章详情等页面展示用户的技能标签或文章关键词。

第一步:基础用法 - 颜色、图标与状态

Tag 组件最基础的用法就是展示文本,并通过不同的颜色来传递语义。Ant Design 为我们内置了多套预设颜色,同时也支持完全自定义。

核心属性:

  • color: 设置标签的颜色。它可以接受三种类型的值:
    1. 预设状态色success, processing, error, warning, default,用于表达明确的状态语义。
    2. 预设调色板色magenta, red, volcano, orange, gold 等,提供了丰富的色彩选择。
    3. 自定义色:标准的 HEXRGB 值,如 #87d068
  • icon: 在标签文本前添加一个图标,增强视觉表现力。

文件路径: src/components/demos/BasicTagDemo.tsx (新建文件)

image-20250929141416678

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 来动态管理标签数组。

核心思路:

  1. 使用 useState 维护一个存储所有标签的数组。
  2. 使用数组的 map 方法,将 state 中的每个标签渲染成一个可关闭的 Tag 组件。
  3. 为每个 TagonClose 事件绑定一个处理函数,该函数从 state 数组中移除对应的标签。
  4. 提供一个 InputButton(或类似交互)来向 state 数组中添加新的标签。

文件路径: src/components/demos/DynamicTagDemo.tsx (新建文件)

img

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,但行为像 CheckboxRadio 的组件,用于筛选或分类选择。为此,antd 提供了一个特殊的子组件:Tag.CheckableTag

核心要点:

  • Tag.CheckableTag 是一个 完全受控 的组件。
  • 它的选中状态由外部传入的 checked 属性决定。
  • 当用户点击时,它会触发 onChange 回调,并传入一个新的布尔值(truefalse),我们需要在这个回调中更新我们自己的 state。

文件路径: src/components/demos/CheckableTagDemo.tsx (新建文件)

img

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 DynamicTagDemo from './components/demos/DynamicTagDemo';
import CheckableTagDemo from './components/demos/CheckableTagDemo';
// ... 其他 import ...

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: 当 count0 时,默认不显示徽标。设置此属性为 true 可以强制显示 0

文件路径: src/components/demos/BasicBadgeDemo.tsx (新建文件)

image-20250929152409427

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 (新建文件)

image-20250929152649819

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.RibbonBadge 的一个特殊子组件,它以“缎带”的形式包裹一个容器(例如 Card),用于标记整个区块的属性,例如“新品”、“推荐”、“折扣”等。

核心属性:

  • text: 显示在缎带上的文本
  • color: 自定义缎带的颜色。
  • placement: 缎带的位置,可选值为 startend(默认为 end)。

文件路径: src/components/demos/RibbonBadgeDemo.tsx (新建文件)

image-20250929154550110

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 StatusBadgeDemo from './components/demos/StatusBadgeDemo';
import RibbonBadgeDemo from './components/demos/RibbonBadgeDemo';
// ... 其他 import ...

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 (新建文件)

image-20250929161526234

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`
  • 工作流程:
    1. Avatar 尝试加载 src 中的图片。
    2. 如果加载失败,它会检查是否传入了 icon 属性。如果传入了,则渲染该图标。
    3. 如果没传入 icon,它会继续检查是否传入了 children。如果传入了,则渲染该字符。
    4. 如果 iconchildren 都没有,则会渲染一个默认的 UserOutlined 图标。

文件路径: src/components/demos/AdvancedAvatarDemo.tsx (新建文件)

image-20250929161557420

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 BasicAvatarDemo from './components/demos/BasicAvatarDemo';
import AdvancedAvatarDemo from './components/demos/AdvancedAvatarDemo';
// ... 其他 import ...

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 (新建文件)

image-20250929161619804

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 AdvancedAvatarDemo from './components/demos/AdvancedAvatarDemo';
import GroupAvatarDemo from './components/demos/GroupAvatarDemo';
// ... 其他 import ...

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 的尺寸。

可用性提示:在自定义 colorbgColor 时,请务必保持足够的颜色对比度,否则可能导致二维码难以被扫描识别。

文件路径: src/components/demos/CustomQRCodeDemo.tsx (新建文件)

image-20250929170753460

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 BasicQRCodeDemo from './components/demos/BasicQRCodeDemo';
import CustomQRCodeDemo from './components/demos/CustomQRCodeDemo';
// ... 其他 import ...

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: 当 statusexpired 时,用户点击“点击刷新”的回调函数。
  • errorLevel: 设置二维码的纠错等级。可选值为 'L', 'M', 'Q', 'H',纠错能力依次增强。

纠错等级 (Error Level):
二维码的一部分被遮挡后仍能被正常扫描的能力。等级越高,可被遮挡的面积越大,但二维码的像素点也会越密集。

  • L: ~7%
  • M: ~15% (默认)
  • Q: ~25%
  • H: ~30%

最佳实践:当您使用 icon 属性在二维码中嵌入图标时,强烈建议errorLevel 设置为 'H'。因为图标本身就遮挡了一部分二维码信息,提高纠错等级可以确保二维码在有遮挡的情况下依然保持高识别率。

文件路径: src/components/demos/AdvancedQRCodeDemo.tsx (新建文件)

img

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 CustomQRCodeDemo from './components/demos/CustomQRCodeDemo';
import AdvancedQRCodeDemo from './components/demos/AdvancedQRCodeDemo';
// ... 其他 import ...

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 会自动收集所有子 Imagesrc,形成一个图片数组。
  • 点击其中任意一张图片,都会以相册模式打开预览,并允许用户在所有图片间导航。

文件路径: 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 BasicImageDemo from './components/demos/BasicImageDemo';
import GroupImageDemo from './components/demos/GroupImageDemo';
// ... 其他 import ...

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 GroupImageDemo from './components/demos/GroupImageDemo';
import ControlledImageDemo from './components/demos/ControlledImageDemo';
// ... 其他 import ...

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 组件的这些高级功能,使其不仅仅是一个图片展示工具,更是一个功能完备的图片交互解决方案。


7.12.6. Tooltip: 轻量级的悬浮提示

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 BasicTooltipDemo from './components/demos/BasicTooltipDemo';
import PlacementTooltipDemo from './components/demos/PlacementTooltipDemo';
// ... 其他 import ...

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>
);
};

通过灵活运用 placementcolor 属性,您可以让 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 (新建文件)

img

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 (新建文件)

img

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 BasicPopoverDemo from './components/demos/BasicPopoverDemo';
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 的基础用法非常直观,通过 titlevalue 两个核心属性即可定义一个统计项。更进一步,我们可以通过前缀、后缀、精度控制等属性,对数值进行丰富的格式化。

核心属性:

  • title: 数值的标题,说明该数字的含义
  • value: 需要展示的核心数值
  • prefix: 在数值 添加的前缀,可以是文本或图标(如货币符号 $)。
  • suffix: 在数值 添加的后缀,可以是文本或图标(如单位 或百分号 %)。
  • precision: 设置数值的小数点精度。
  • valueStyle: 用于单独设置数值区域的 CSS 样式,常用于根据数值正负改变颜色。

文件路径: src/components/demos/BasicStatisticDemo.tsx (新建文件)

img

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 (新建文件)

img

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;

// 设置一个未来时间点用于倒计时(当前时间 + 30秒)
const deadline = dayjs().add(30, 'second').valueOf();
// 设置一个过去时间点用于正计时(当前时间 - 1小时)
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 BasicStatisticDemo from './components/demos/BasicStatisticDemo';
import TimerStatisticDemo from './components/demos/TimerStatisticDemo';
// ... 其他 import ...

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 内部封装了 requestAnimationFramesetInterval 等复杂的计时和重渲染逻辑,让我们能够以极低的成本,在界面中实现精准、高效的动态计时功能。掌握 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 (新建文件)

image-20250930083817771

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 (新建文件)

image-20250930084644391

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 BasicEmptyDemo from './components/demos/BasicEmptyDemo';
import CustomizeEmptyDemo from './components/demos/CustomizeEmptyDemo';
// ... 其他 import ...

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>
);
};

通过自定义 imagedescriptionchildren,我们可以创造出完全符合产品调性和业务逻辑的空状态,将原本可能导致用户流失的空白页面,变成提升用户体验和转化率的有效触点。

至此,我们已经完成了第一阶段所有基础数据展示组件的学习。接下来,我们将进入 阶段二:内容容器组件,学习如何将这些基础组件有效地组织起来。