Skip to content

Ant Design

Ant Design(antd)是蚂蚁集团出品的企业级 React UI 组件库,内置 60+ 高质量组件,开箱即用,特别适合中后台管理系统。当前主流版本为 v5,基于 CSS-in-JS 实现主题系统。


安装

npm install antd

v5 不需要额外引入 CSS 文件,开箱即用:

// main.tsx 或 App.tsx
import { Button } from "antd"

export default function App() {
  return <Button type="primary">Hello Antd</Button>
}

按需加载

v5 默认支持 Tree Shaking,直接按需 import 即可,无需额外配置:

import { Button, Input, Table } from "antd"

主题定制

ConfigProvider 全局配置

ConfigProvider 是 antd 的全局配置入口,包裹在应用根部:

// App.tsx
import { ConfigProvider } from "antd"
import zhCN from "antd/locale/zh_CN"

export default function App() {
  return (
    <ConfigProvider
      locale={zhCN}
      theme={{
        token: {
          // 主色
          colorPrimary: "#1677ff",
          colorSuccess: "#52c41a",
          colorWarning: "#faad14",
          colorError: "#ff4d4f",
          colorInfo: "#1677ff",

          // 圆角
          borderRadius: 6,

          // 字体
          fontSize: 14,
          fontFamily: "'PingFang SC', 'Microsoft YaHei', sans-serif",

          // 线条
          lineWidth: 1,

          // 间距
          padding: 16,
          paddingLG: 24,
          paddingSM: 12,
        },
        components: {
          // 覆盖单个组件的 token
          Button: {
            colorPrimary: "#00b96b",
            borderRadius: 8,
            controlHeight: 40,
          },
          Table: {
            borderRadius: 8,
            headerBg: "#f5f5f5",
          },
        },
      }}
    >
      <RouterProvider router={router} />
    </ConfigProvider>
  )
}

暗色模式

import { ConfigProvider, theme } from "antd"

const { darkAlgorithm, compactAlgorithm, defaultAlgorithm } = theme

function App() {
  const [isDark, setIsDark] = useState(false)

  return (
    <ConfigProvider
      theme={{
        algorithm: isDark ? darkAlgorithm : defaultAlgorithm,
        // 同时开启暗色 + 紧凑
        // algorithm: [darkAlgorithm, compactAlgorithm],
      }}
    >
      <button onClick={() => setIsDark(!isDark)}>切换主题</button>
      <YourApp />
    </ConfigProvider>
  )
}

useToken:在组件中读取主题变量

import { theme } from "antd"

function MyComponent() {
  const { token } = theme.useToken()

  return (
    <div
      style={{
        background: token.colorBgContainer,
        borderRadius: token.borderRadius,
        padding: token.padding,
        color: token.colorText,
      }}
    >
      使用主题变量
    </div>
  )
}

布局组件

Layout

import { Layout, Menu, theme } from "antd"

const { Header, Content, Footer, Sider } = Layout

function AdminLayout() {
  const [collapsed, setCollapsed] = useState(false)
  const { token } = theme.useToken()

  return (
    <Layout style={{ minHeight: "100vh" }}>
      {/* 侧边栏 */}
      <Sider
        collapsible
        collapsed={collapsed}
        onCollapse={setCollapsed}
        theme="light"
        width={220}
      >
        <div style={{ height: 32, margin: 16, background: "rgba(0,0,0,.1)", borderRadius: 6 }} />
        <Menu
          mode="inline"
          defaultSelectedKeys={["1"]}
          items={[
            { key: "1", icon: <DashboardOutlined />, label: "仪表盘" },
            { key: "2", icon: <UserOutlined />, label: "用户管理" },
            {
              key: "sub1",
              icon: <SettingOutlined />,
              label: "系统设置",
              children: [
                { key: "3", label: "基本设置" },
                { key: "4", label: "权限管理" },
              ],
            },
          ]}
        />
      </Sider>

      <Layout>
        <Header style={{ padding: "0 24px", background: token.colorBgContainer, display: "flex", alignItems: "center" }}>
          <h2 style={{ margin: 0 }}>管理系统</h2>
        </Header>
        <Content style={{ margin: "24px 16px", padding: 24, background: token.colorBgContainer, borderRadius: token.borderRadius }}>
          <Outlet />
        </Content>
        <Footer style={{ textAlign: "center" }}>©2025 My Company</Footer>
      </Layout>
    </Layout>
  )
}

Grid 栅格

antd 栅格系统基于 24 列:

import { Row, Col } from "antd"

{/* 基础用法:24 列 */}
<Row gutter={16}>
  <Col span={8}>占 8 列(1/3)</Col>
  <Col span={8}>占 8 列(1/3)</Col>
  <Col span={8}>占 8 列(1/3)</Col>
</Row>

{/* 响应式 */}
<Row gutter={{ xs: 8, sm: 16, md: 24 }}>
  <Col xs={24} sm={12} md={8} lg={6}>响应式列</Col>
  <Col xs={24} sm={12} md={8} lg={6}>响应式列</Col>
  <Col xs={24} sm={12} md={8} lg={6}>响应式列</Col>
  <Col xs={24} sm={12} md={8} lg={6}>响应式列</Col>
</Row>

{/* 偏移 */}
<Row>
  <Col span={8}>左侧</Col>
  <Col span={8} offset={8}>右侧(偏移 8 列)</Col>
</Row>

{/* Flex 对齐 */}
<Row justify="space-between" align="middle">
  <Col span={4}>左</Col>
  <Col span={4}>中</Col>
  <Col span={4}>右</Col>
</Row>

Space 间距

import { Space, Divider } from "antd"

{/* 水平间距 */}
<Space size="middle">
  <Button>按钮一</Button>
  <Button>按钮二</Button>
  <Button>按钮三</Button>
</Space>

{/* 垂直间距 */}
<Space direction="vertical" size={16} style={{ display: "flex" }}>
  <Input placeholder="用户名" />
  <Input placeholder="密码" />
  <Button type="primary" block>登录</Button>
</Space>

{/* 自动换行 */}
<Space size={[8, 16]} wrap>
  {tags.map(tag => <Tag key={tag}>{tag}</Tag>)}
</Space>

{/* 带分隔符 */}
<Space split={<Divider type="vertical" />}>
  <a>编辑</a>
  <a>删除</a>
  <a>查看</a>
</Space>

通用组件

Button 按钮

import { Button, Space } from "antd"

{/* 类型 */}
<Button type="primary">主要按钮</Button>
<Button type="default">默认按钮</Button>
<Button type="dashed">虚线按钮</Button>
<Button type="text">文字按钮</Button>
<Button type="link">链接按钮</Button>

{/* 危险状态 */}
<Button danger>危险</Button>
<Button type="primary" danger>主要危险</Button>

{/* 尺寸 */}
<Button size="large">大</Button>
<Button size="middle">中(默认)</Button>
<Button size="small">小</Button>

{/* 状态 */}
<Button loading>加载中</Button>
<Button disabled>禁用</Button>
<Button block>通栏按钮</Button>

{/* 图标 */}
<Button icon={<SearchOutlined />}>搜索</Button>
<Button type="primary" shape="circle" icon={<PlusOutlined />} />
<Button type="primary" shape="round" icon={<DownloadOutlined />}>下载</Button>

Typography 排版

import { Typography } from "antd"
const { Title, Text, Paragraph, Link } = Typography

<Typography>
  <Title>一级标题</Title>
  <Title level={2}>二级标题</Title>
  <Title level={3}>三级标题</Title>

  <Paragraph>
    这是一段普通段落文字,<Text strong>加粗</Text>、
    <Text italic>斜体</Text>、<Text underline>下划线</Text>、
    <Text delete>删除线</Text>、<Text code>行内代码</Text>、
    <Text type="secondary">次要文字</Text>、
    <Text type="success">成功</Text>、
    <Text type="warning">警告</Text>、
    <Text type="danger">危险</Text>。
  </Paragraph>

  {/* 可复制 */}
  <Paragraph copyable>可复制的文本内容</Paragraph>
  <Paragraph copyable={{ text: "自定义复制内容" }}>自定义复制</Paragraph>

  {/* 省略 */}
  <Paragraph ellipsis={{ rows: 2, expandable: true, symbol: "展开" }}>
    这是一段很长的文字,超过两行后会自动折叠,点击展开可以查看全部内容...
  </Paragraph>

  {/* 可编辑 */}
  <Paragraph editable={{ onChange: (val) => console.log(val) }}>
    可以双击编辑这段文字
  </Paragraph>

  <Link href="https://ant.design" target="_blank">Ant Design 官网</Link>
</Typography>

Icon 图标

antd v5 使用 @ant-design/icons

npm install @ant-design/icons
import {
  UserOutlined, SearchOutlined, PlusOutlined,
  EditOutlined, DeleteOutlined, SettingOutlined,
  CheckCircleOutlined, CloseCircleOutlined,
  LoadingOutlined, DownloadOutlined, UploadOutlined,
} from "@ant-design/icons"

<UserOutlined />
<SearchOutlined style={{ fontSize: 20, color: "#1677ff" }} />

{/* 旋转动画 */}
<LoadingOutlined spin />

{/* 自定义图标 */}
import Icon from "@ant-design/icons"
const HeartSvg = () => <svg>...</svg>
<Icon component={HeartSvg} />

数据录入

Form 表单

antd Form 是最常用也最复杂的组件之一:

import { Form, Input, Button, Select, Checkbox, DatePicker, InputNumber, Radio, Switch, Upload } from "antd"

interface FormValues {
  username: string
  password: string
  email: string
  role: string
  age: number
  agree: boolean
}

function RegisterForm() {
  const [form] = Form.useForm<FormValues>()

  async function onFinish(values: FormValues) {
    console.log("提交:", values)
    await submitForm(values)
  }

  return (
    <Form
      form={form}
      layout="vertical"           // horizontal | vertical | inline
      onFinish={onFinish}
      onFinishFailed={({ errorFields }) => console.log("验证失败:", errorFields)}
      initialValues={{ role: "user", agree: false }}
      autoComplete="off"
    >
      <Form.Item
        name="username"
        label="用户名"
        rules={[
          { required: true, message: "请输入用户名" },
          { min: 2, message: "至少 2 个字符" },
          { max: 20, message: "最多 20 个字符" },
          { pattern: /^[a-zA-Z0-9_]+$/, message: "只能包含字母、数字、下划线" },
        ]}
      >
        <Input placeholder="请输入用户名" prefix={<UserOutlined />} />
      </Form.Item>

      <Form.Item
        name="password"
        label="密码"
        rules={[
          { required: true, message: "请输入密码" },
          { min: 8, message: "密码至少 8 位" },
        ]}
      >
        <Input.Password placeholder="请输入密码" />
      </Form.Item>

      <Form.Item
        name="confirm"
        label="确认密码"
        dependencies={["password"]}
        rules={[
          { required: true, message: "请确认密码" },
          ({ getFieldValue }) => ({
            validator(_, value) {
              if (!value || getFieldValue("password") === value) {
                return Promise.resolve()
              }
              return Promise.reject(new Error("两次密码不一致"))
            },
          }),
        ]}
      >
        <Input.Password placeholder="请再次输入密码" />
      </Form.Item>

      <Form.Item name="email" label="邮箱" rules={[{ type: "email", message: "邮箱格式不正确" }, { required: true }]}>
        <Input placeholder="name@example.com" />
      </Form.Item>

      <Form.Item name="role" label="角色">
        <Select
          options={[
            { value: "admin", label: "管理员" },
            { value: "user", label: "普通用户" },
            { value: "guest", label: "访客" },
          ]}
        />
      </Form.Item>

      <Form.Item name="age" label="年龄">
        <InputNumber min={1} max={120} style={{ width: "100%" }} />
      </Form.Item>

      <Form.Item name="agree" valuePropName="checked" rules={[{ validator: (_, v) => v ? Promise.resolve() : Promise.reject("请阅读并同意协议") }]}>
        <Checkbox>我已阅读并同意<a href="#">用户协议</a></Checkbox>
      </Form.Item>

      <Form.Item>
        <Button type="primary" htmlType="submit" block>注册</Button>
      </Form.Item>
    </Form>
  )
}

Form 动态字段

<Form.List name="phones">
  {(fields, { add, remove }) => (
    <>
      {fields.map(({ key, name, ...restField }) => (
        <Space key={key} align="baseline">
          <Form.Item
            {...restField}
            name={[name, "type"]}
            rules={[{ required: true, message: "请选择类型" }]}
          >
            <Select options={[{ value: "mobile", label: "手机" }, { value: "work", label: "工作" }]} style={{ width: 100 }} />
          </Form.Item>
          <Form.Item
            {...restField}
            name={[name, "number"]}
            rules={[{ required: true, message: "请输入电话" }]}
          >
            <Input placeholder="电话号码" />
          </Form.Item>
          <MinusCircleOutlined onClick={() => remove(name)} />
        </Space>
      ))}
      <Form.Item>
        <Button type="dashed" onClick={() => add()} icon={<PlusOutlined />} block>
          添加电话
        </Button>
      </Form.Item>
    </>
  )}
</Form.List>

Form 命令式操作

const [form] = Form.useForm()

// 设置字段值
form.setFieldValue("username", "张三")
form.setFieldsValue({ username: "张三", email: "zhang@example.com" })

// 读取字段值
const username = form.getFieldValue("username")
const all = form.getFieldsValue()

// 手动触发校验
const values = await form.validateFields()
await form.validateFields(["username", "email"])  // 只校验指定字段

// 重置表单
form.resetFields()
form.resetFields(["username"])

// 设置字段错误
form.setFields([{ name: "username", errors: ["用户名已存在"] }])

Input 输入框

import { Input } from "antd"

<Input placeholder="普通输入框" />
<Input.Password placeholder="密码输入框" />
<Input.TextArea rows={4} placeholder="多行文本" maxLength={200} showCount />
<Input.Search placeholder="搜索" onSearch={(val) => console.log(val)} enterButton />

{/* 前缀/后缀 */}
<Input prefix={<UserOutlined />} suffix={<Tooltip title="提示"><InfoCircleOutlined /></Tooltip>} />

{/* 前置/后置标签 */}
<Input addonBefore="http://" addonAfter=".com" placeholder="网址" />
<Input addonBefore={
  <Select defaultValue="https://" style={{ width: 90 }} options={[{ value: "http://" }, { value: "https://" }]} />
} />

Select 选择器

import { Select } from "antd"

{/* 基础 */}
<Select
  style={{ width: 200 }}
  placeholder="请选择"
  options={[
    { value: "1", label: "选项一" },
    { value: "2", label: "选项二", disabled: true },
  ]}
  onChange={(value, option) => console.log(value, option)}
/>

{/* 多选 */}
<Select mode="multiple" allowClear style={{ width: "100%" }} placeholder="可多选" options={options} />

{/* 标签模式(可自由输入) */}
<Select mode="tags" style={{ width: "100%" }} placeholder="输入后回车添加标签" />

{/* 可搜索 */}
<Select showSearch filterOption={(input, option) => (option?.label ?? "").toLowerCase().includes(input.toLowerCase())} options={options} />

{/* 远程搜索 */}
<Select
  showSearch
  filterOption={false}
  onSearch={fetchOptions}    // 输入时请求
  notFoundContent={loading ? <Spin size="small" /> : null}
  options={options}
/>

{/* 分组 */}
<Select options={[
  { label: "一组", options: [{ value: "a", label: "A" }, { value: "b", label: "B" }] },
  { label: "二组", options: [{ value: "c", label: "C" }] },
]} />

DatePicker 日期选择

import { DatePicker, Space } from "antd"
import dayjs from "dayjs"

<DatePicker onChange={(date, dateStr) => console.log(dateStr)} />
<DatePicker.RangePicker />
<DatePicker showTime placeholder="选择日期时间" />

{/* 格式化 */}
<DatePicker format="YYYY年MM月DD日" />

{/* 预设范围 */}
<DatePicker.RangePicker
  presets={[
    { label: "最近 7 天", value: [dayjs().subtract(7, "d"), dayjs()] },
    { label: "最近 30 天", value: [dayjs().subtract(30, "d"), dayjs()] },
    { label: "本月", value: [dayjs().startOf("month"), dayjs().endOf("month")] },
  ]}
/>

{/* 禁用日期 */}
<DatePicker disabledDate={(current) => current && current < dayjs().startOf("day")} />

Upload 上传

import { Upload, Button, message } from "antd"
import type { UploadProps } from "antd"

const props: UploadProps = {
  name: "file",
  action: "/api/upload",
  headers: { authorization: `Bearer ${token}` },
  accept: "image/*",
  maxCount: 3,
  onChange({ file, fileList }) {
    if (file.status === "done") {
      message.success(`${file.name} 上传成功`)
    } else if (file.status === "error") {
      message.error(`${file.name} 上传失败`)
    }
  },
  beforeUpload(file) {
    const isLt2M = file.size / 1024 / 1024 < 2
    if (!isLt2M) message.error("图片不能超过 2MB")
    return isLt2M
  },
}

{/* 点击上传 */}
<Upload {...props}>
  <Button icon={<UploadOutlined />}>选择文件</Button>
</Upload>

{/* 拖拽上传 */}
<Upload.Dragger {...props}>
  <p className="ant-upload-drag-icon"><InboxOutlined /></p>
  <p>点击或拖拽文件到此区域上传</p>
  <p className="ant-upload-hint">支持单个或批量上传</p>
</Upload.Dragger>

{/* 图片上传预览 */}
<Upload
  listType="picture-card"
  fileList={fileList}
  onPreview={handlePreview}
  onChange={({ fileList }) => setFileList(fileList)}
>
  {fileList.length < 8 && <div><PlusOutlined /><div>上传</div></div>}
</Upload>

数据展示

Table 表格

import { Table, Tag, Space, Button, Popconfirm } from "antd"
import type { TableProps, TableColumnsType } from "antd"

interface User {
  key: string
  name: string
  age: number
  role: string
  status: "active" | "inactive"
  createdAt: string
}

const columns: TableColumnsType<User> = [
  {
    title: "姓名",
    dataIndex: "name",
    key: "name",
    sorter: (a, b) => a.name.localeCompare(b.name),
    render: (name) => <a>{name}</a>,
  },
  {
    title: "年龄",
    dataIndex: "age",
    key: "age",
    sorter: (a, b) => a.age - b.age,
    width: 80,
  },
  {
    title: "角色",
    dataIndex: "role",
    key: "role",
    filters: [
      { text: "管理员", value: "admin" },
      { text: "普通用户", value: "user" },
    ],
    onFilter: (value, record) => record.role === value,
  },
  {
    title: "状态",
    dataIndex: "status",
    key: "status",
    render: (status) => (
      <Tag color={status === "active" ? "green" : "red"}>
        {status === "active" ? "正常" : "禁用"}
      </Tag>
    ),
  },
  {
    title: "操作",
    key: "action",
    fixed: "right",
    width: 160,
    render: (_, record) => (
      <Space>
        <Button type="link" size="small" onClick={() => handleEdit(record)}>编辑</Button>
        <Popconfirm
          title="确认删除?"
          description="删除后无法恢复"
          onConfirm={() => handleDelete(record.key)}
          okText="确认"
          cancelText="取消"
        >
          <Button type="link" size="small" danger>删除</Button>
        </Popconfirm>
      </Space>
    ),
  },
]

function UserTable() {
  const [selectedRowKeys, setSelectedRowKeys] = useState<React.Key[]>([])
  const [pagination, setPagination] = useState({ current: 1, pageSize: 10, total: 0 })

  return (
    <Table
      columns={columns}
      dataSource={users}
      rowKey="key"
      loading={loading}
      scroll={{ x: 1000 }}
      pagination={{
        ...pagination,
        showSizeChanger: true,
        showQuickJumper: true,
        showTotal: (total) => `共 ${total} 条`,
        onChange: (page, pageSize) => fetchUsers({ page, pageSize }),
      }}
      rowSelection={{
        selectedRowKeys,
        onChange: setSelectedRowKeys,
        selections: [Table.SELECTION_ALL, Table.SELECTION_NONE],
      }}
      summary={(data) => (
        <Table.Summary.Row>
          <Table.Summary.Cell index={0} colSpan={2}>汇总</Table.Summary.Cell>
          <Table.Summary.Cell index={2}>
            <Text type="danger">{data.reduce((sum, r) => sum + r.age, 0)}</Text>
          </Table.Summary.Cell>
        </Table.Summary.Row>
      )}
    />
  )
}

List 列表

import { List, Avatar, Card } from "antd"

{/* 基础列表 */}
<List
  itemLayout="horizontal"
  dataSource={users}
  loading={loading}
  pagination={{ pageSize: 10 }}
  renderItem={(item) => (
    <List.Item
      actions={[<a key="edit">编辑</a>, <a key="delete">删除</a>]}
    >
      <List.Item.Meta
        avatar={<Avatar src={item.avatar} />}
        title={<a href="#">{item.name}</a>}
        description={item.email}
      />
      <div>{item.role}</div>
    </List.Item>
  )}
/>

{/* 卡片列表(Grid 模式) */}
<List
  grid={{ gutter: 16, xs: 1, sm: 2, md: 3, lg: 4 }}
  dataSource={items}
  renderItem={(item) => (
    <List.Item>
      <Card title={item.title}>{item.description}</Card>
    </List.Item>
  )}
/>

Descriptions 描述列表

import { Descriptions, Badge } from "antd"
import type { DescriptionsProps } from "antd"

const items: DescriptionsProps["items"] = [
  { key: "1", label: "用户名",   children: "张三" },
  { key: "2", label: "邮箱",     children: "zhang@example.com" },
  { key: "3", label: "角色",     children: "管理员" },
  { key: "4", label: "手机",     children: "138****8888" },
  { key: "5", label: "状态",     children: <Badge status="processing" text="正常" /> },
  { key: "6", label: "注册时间", children: "2024-01-01 10:00:00" },
  { key: "7", label: "备注",     children: "这是一段较长的备注内容", span: 3 },
]

<Descriptions
  title="用户详情"
  bordered
  column={{ xs: 1, sm: 2, md: 3 }}
  items={items}
  extra={<Button type="primary">编辑</Button>}
/>

Statistic 统计数值

import { Statistic, Card, Row, Col } from "antd"
import { ArrowUpOutlined, ArrowDownOutlined } from "@ant-design/icons"
import CountUp from "react-countup"

<Row gutter={16}>
  <Col span={6}>
    <Card>
      <Statistic title="活跃用户" value={11280} prefix={<UserOutlined />} />
    </Card>
  </Col>
  <Col span={6}>
    <Card>
      <Statistic title="今日收入" value={9280} precision={2} prefix="¥" suffix="元"
        valueStyle={{ color: "#3f8600" }}
        prefix={<ArrowUpOutlined />}
      />
    </Card>
  </Col>
  <Col span={6}>
    <Card>
      <Statistic title="退款率" value={2.3} precision={1} suffix="%" valueStyle={{ color: "#cf1322" }} prefix={<ArrowDownOutlined />} />
    </Card>
  </Col>
  <Col span={6}>
    <Card>
      {/* 动画数字 */}
      <Statistic title="总订单" value={112893} formatter={(v) => <CountUp end={Number(v)} duration={2} separator="," />} />
    </Card>
  </Col>
</Row>

Tag 标签

import { Tag, Space } from "antd"

{/* 基础 */}
<Tag>默认</Tag>
<Tag color="magenta">品红</Tag>
<Tag color="red">红色</Tag>
<Tag color="orange">橙色</Tag>
<Tag color="green">绿色</Tag>
<Tag color="blue">蓝色</Tag>
<Tag color="purple">紫色</Tag>

{/* 状态色 */}
<Tag color="success">成功</Tag>
<Tag color="processing">处理中</Tag>
<Tag color="error">错误</Tag>
<Tag color="warning">警告</Tag>

{/* 可关闭 */}
<Tag closable onClose={() => handleClose()}>可关闭标签</Tag>

{/* 动态增删 */}
{tags.map(tag => <Tag key={tag} closable onClose={() => removeTag(tag)}>{tag}</Tag>)}
<Input size="small" style={{ width: 80 }} onPressEnter={(e) => addTag(e.currentTarget.value)} />

Progress 进度条

import { Progress } from "antd"

<Progress percent={60} />
<Progress percent={100} status="success" />
<Progress percent={70} status="exception" />
<Progress percent={50} showInfo={false} />

{/* 环形 */}
<Progress type="circle" percent={75} />
<Progress type="circle" percent={100} status="success" />
<Progress type="circle" percent={70} format={(percent) => `${percent}分`} />

{/* 仪表盘 */}
<Progress type="dashboard" percent={75} />

{/* 步骤进度 */}
<Progress percent={60} steps={5} />

{/* 渐变色 */}
<Progress percent={80} strokeColor={{ "0%": "#108ee9", "100%": "#87d068" }} />

Tree 树形控件

import { Tree } from "antd"
import type { TreeDataNode } from "antd"

const treeData: TreeDataNode[] = [
  {
    title: "技术部",
    key: "0",
    children: [
      {
        title: "前端组",
        key: "0-0",
        children: [
          { title: "张三", key: "0-0-0", isLeaf: true },
          { title: "李四", key: "0-0-1", isLeaf: true },
        ],
      },
      { title: "后端组", key: "0-1", children: [{ title: "王五", key: "0-1-0", isLeaf: true }] },
    ],
  },
]

<Tree
  checkable
  defaultExpandAll
  treeData={treeData}
  onCheck={(checked) => console.log(checked)}
  onSelect={(selected, { node }) => console.log(node.title)}
/>

反馈组件

import { Modal, Button, Form, Input } from "antd"

{/* 声明式 */}
<Modal
  title="编辑用户"
  open={open}
  onOk={handleOk}
  onCancel={() => setOpen(false)}
  confirmLoading={loading}
  width={600}
  footer={[
    <Button key="cancel" onClick={() => setOpen(false)}>取消</Button>,
    <Button key="submit" type="primary" loading={loading} onClick={handleOk}>确认</Button>,
  ]}
>
  <Form form={form} layout="vertical">
    <Form.Item name="name" label="姓名"><Input /></Form.Item>
  </Form>
</Modal>

{/* 命令式(无需维护 open 状态) */}
import { App } from "antd"

function MyComponent() {
  const { modal } = App.useApp()

  function confirm() {
    modal.confirm({
      title: "确认删除?",
      content: "删除后数据无法恢复",
      okText: "确认",
      okType: "danger",
      cancelText: "取消",
      onOk: async () => {
        await deleteItem()
      },
    })
  }
}

Drawer 抽屉

import { Drawer, Button, Form } from "antd"

<Drawer
  title="新建用户"
  placement="right"
  width={500}
  open={open}
  onClose={() => setOpen(false)}
  extra={
    <Space>
      <Button onClick={() => setOpen(false)}>取消</Button>
      <Button type="primary" onClick={handleSubmit}>提交</Button>
    </Space>
  }
>
  <Form layout="vertical">
    {/* 表单内容 */}
  </Form>
</Drawer>

Message 全局消息

import { App } from "antd"

// 推荐:通过 App 上下文使用(支持主题)
function MyComponent() {
  const { message } = App.useApp()

  function handleClick() {
    message.success("操作成功")
    message.error("操作失败")
    message.warning("注意事项")
    message.info("提示信息")
    message.loading("加载中...", 0)  // 0 表示不自动关闭

    // Promise 用法
    const hide = message.loading("提交中...", 0)
    await submitForm()
    hide()
    message.success("提交成功")
  }
}

// App 包裹
function App() {
  return (
    <ConfigProvider>
      <AntdApp>  {/* 必须包裹才能使用 useApp() */}
        <RouterProvider router={router} />
      </AntdApp>
    </ConfigProvider>
  )
}

Notification 通知提醒

import { App } from "antd"

function MyComponent() {
  const { notification } = App.useApp()

  function openNotification() {
    notification.success({
      message: "操作成功",
      description: "用户信息已更新,变更将在下次登录后生效。",
      placement: "topRight",   // topLeft | topRight | bottomLeft | bottomRight
      duration: 4.5,
      btn: (
        <Button type="primary" size="small" onClick={() => notification.destroy()}>
          确认
        </Button>
      ),
    })

    notification.error({ message: "请求失败", description: "网络连接超时,请稍后重试" })
  }
}

Popconfirm 气泡确认

import { Popconfirm, Button } from "antd"

<Popconfirm
  title="确认删除"
  description="此操作不可撤销,确定要删除吗?"
  onConfirm={() => handleDelete(id)}
  onCancel={() => console.log("取消")}
  okText="确认"
  cancelText="取消"
  okType="danger"
>
  <Button danger>删除</Button>
</Popconfirm>

Spin 加载中

import { Spin } from "antd"

{/* 整页加载 */}
<Spin spinning={loading} fullscreen />

{/* 包裹内容 */}
<Spin spinning={loading} tip="加载中...">
  <div style={{ padding: 50 }}>需要加载的内容</div>
</Spin>

{/* 自定义指示符 */}
<Spin indicator={<LoadingOutlined style={{ fontSize: 24 }} spin />} />

Result 结果

import { Result, Button } from "antd"

<Result
  status="success"
  title="提交成功!"
  subTitle="订单号:2024123456,预计 3 天内完成"
  extra={[
    <Button type="primary" key="home">回到首页</Button>,
    <Button key="order">查看订单</Button>,
  ]}
/>

<Result status="404" title="404" subTitle="您访问的页面不存在" extra={<Button type="primary">返回首页</Button>} />
<Result status="500" title="500" subTitle="服务器错误" extra={<Button type="primary">刷新页面</Button>} />
<Result status="403" title="403" subTitle="无访问权限" />

导航组件

import { Menu } from "antd"
import type { MenuProps } from "antd"
import { useNavigate, useLocation } from "react-router"

type MenuItem = Required<MenuProps>["items"][number]

const items: MenuItem[] = [
  { key: "/",          icon: <DashboardOutlined />, label: "仪表盘" },
  { key: "/users",     icon: <UserOutlined />,      label: "用户管理" },
  { key: "/orders",   icon: <ShoppingOutlined />,  label: "订单管理" },
  {
    key: "setting",
    icon: <SettingOutlined />,
    label: "系统设置",
    children: [
      { key: "/settings/basic",  label: "基本设置" },
      { key: "/settings/roles",  label: "角色权限" },
      { key: "/settings/logs",   label: "操作日志" },
    ],
  },
]

function SideMenu() {
  const navigate = useNavigate()
  const location = useLocation()

  return (
    <Menu
      mode="inline"
      selectedKeys={[location.pathname]}
      defaultOpenKeys={["setting"]}
      items={items}
      onClick={({ key }) => navigate(key)}
    />
  )
}
import { Breadcrumb } from "antd"

<Breadcrumb
  items={[
    { title: <HomeOutlined />, href: "/" },
    { title: "用户管理", href: "/users" },
    { title: "张三" },
  ]}
/>

{/* 搭配路由自动生成 */}
<Breadcrumb
  items={[
    { title: "首页", onClick: () => navigate("/") },
    ...breadcrumbs.map((b) => ({
      title: b.label,
      onClick: b.path ? () => navigate(b.path) : undefined,
    })),
  ]}
/>

Tabs 标签页

import { Tabs } from "antd"

<Tabs
  defaultActiveKey="1"
  type="card"   // line(默认)| card | editable-card
  items={[
    { key: "1", label: "基本信息", children: <BasicInfo /> },
    { key: "2", label: "安全设置", children: <SecuritySettings /> },
    { key: "3", label: "操作日志", children: <OperationLog />, disabled: !isAdmin },
  ]}
  onChange={(key) => console.log(key)}
/>

Steps 步骤条

import { Steps } from "antd"

<Steps
  current={1}
  items={[
    { title: "填写信息", description: "基本资料" },
    { title: "身份认证", description: "实名认证" },
    { title: "完成注册", description: "账号已激活" },
  ]}
/>

{/* 竖向步骤条(带内容) */}
<Steps
  direction="vertical"
  current={current}
  items={steps.map((s) => ({
    title: s.title,
    description: current >= s.index ? <s.content /> : null,
  }))}
/>

实用 Hook

useApp

import { App } from "antd"

// 在根组件包裹 App
function Root() {
  return <App><RouterProvider router={router} /></App>
}

// 在子组件中使用
function MyPage() {
  const { message, modal, notification } = App.useApp()

  async function handleDelete() {
    await modal.confirm({ title: "确认删除?" })
    await deleteItem()
    message.success("删除成功")
  }
}

表格常用封装

// 封装带分页、搜索、增删改查的通用 Hook
function useTable<T>(fetchFn: (params: any) => Promise<{ list: T[]; total: number }>) {
  const [data, setData] = useState<T[]>([])
  const [loading, setLoading] = useState(false)
  const [pagination, setPagination] = useState({ current: 1, pageSize: 10, total: 0 })

  async function fetch(params = {}) {
    setLoading(true)
    try {
      const res = await fetchFn({ ...params, page: pagination.current, pageSize: pagination.pageSize })
      setData(res.list)
      setPagination(p => ({ ...p, total: res.total }))
    } finally {
      setLoading(false)
    }
  }

  useEffect(() => { fetch() }, [pagination.current, pagination.pageSize])

  const tableProps = {
    dataSource: data,
    loading,
    pagination: {
      ...pagination,
      showSizeChanger: true,
      showTotal: (total: number) => `共 ${total} 条`,
      onChange: (page: number, pageSize: number) => setPagination(p => ({ ...p, current: page, pageSize })),
    },
  }

  return { tableProps, fetch, loading }
}

// 使用
function UserPage() {
  const { tableProps, fetch } = useTable(fetchUsers)

  return (
    <>
      <Button onClick={fetch}>刷新</Button>
      <Table {...tableProps} columns={columns} rowKey="id" />
    </>
  )
}

常用组件速查

类别 组件 用途
布局 Layout / Sider 页面整体布局
Row / Col 24 列栅格
Space 子元素间距
Divider 分隔线
通用 Button 按钮
Icon 图标(@ant-design/icons)
Typography 标题/文本/段落
数据录入 Form 表单(含校验)
Input / Input.Password / Input.TextArea 输入框
Select 选择器
DatePicker / RangePicker 日期/范围选择
InputNumber 数字输入
Checkbox / Radio 复选框/单选
Switch 开关
Slider 滑块
Upload 文件上传
Transfer 穿梭框
TreeSelect 树形选择
数据展示 Table 表格(排序/筛选/分页)
List 列表
Descriptions 描述列表
Statistic 统计数值
Tag 标签
Badge 徽标
Avatar 头像
Image 图片(含预览)
Tree 树形展示
Timeline 时间轴
Progress 进度条
Calendar 日历
Card 卡片
Collapse 折叠面板
Carousel 走马灯
Popover 气泡卡片
Tooltip 文字提示
反馈 Modal 对话框
Drawer 抽屉
Message 全局消息
Notification 通知提醒
Popconfirm 气泡确认
Spin 加载中
Skeleton 骨架屏
Result 结果页
Alert 警告提示
Empty 空状态
导航 Menu 菜单
Breadcrumb 面包屑
Tabs 标签页
Steps 步骤条
Pagination 分页
Anchor 锚点