粉粉蕉的笔记本粉粉蕉的笔记本
  • JAVA

    • 代码笔记
    • Java8实战
    • 分布式事务实战(Seata)
    • 模板引擎(FreeMarker)
    • SpringSecurity
    • Maven
  • PYTHON

    • 概述
    • Python3 基础
    • Pandas
    • Numpy
    • Matplotlib
  • 中间件

    • Kafka
    • RocketMQ
    • Redis
    • MongoDB
    • Elastic Search
  • 数据库

    • Mysql
  • 前端

    • HTML
    • CSS
    • Javascript
    • Vue2学习笔记
    • Vue3学习笔记
    • React学习笔记
  • 设计模式
  • 大数据

    • 概览
    • Hadoop
    • Hive
  • 机器学习

    • 机器学习概览
  • openclaw实战
  • claudecode实战
  • RAG
  • 拟人类Agent
  • linux命令速查
  • windows命令速查
  • Docker笔记
  • kubernetes学习笔记
  • kubernetes实操笔记
  • 运维工具大全
  • git操作宝典
  • 概率论
  • 线性代数
  • 统计学
  • 金融知识学习
  • 聚宽
  • 因子分析
  • 后端

    • JAVA基础
    • JAVA多线程
    • JVM
    • 分布式相关
    • 数据库
  • 前端

    • HTML
    • CSS
    • JAVASCRIPT
    • VUE3
    • 网络
    • 前端工程化
    • nodejs
  • AI

    • RAG
  • 健身

    • 笔记
    • 训练计划
  • 读书笔记

    • 《深度学习》
  • 其他

    • RSS
    • 资源导航
    • 医保
    • 装修攻略
我也想搭建这样的博客!
🚋开往
  • JAVA

    • 代码笔记
    • Java8实战
    • 分布式事务实战(Seata)
    • 模板引擎(FreeMarker)
    • SpringSecurity
    • Maven
  • PYTHON

    • 概述
    • Python3 基础
    • Pandas
    • Numpy
    • Matplotlib
  • 中间件

    • Kafka
    • RocketMQ
    • Redis
    • MongoDB
    • Elastic Search
  • 数据库

    • Mysql
  • 前端

    • HTML
    • CSS
    • Javascript
    • Vue2学习笔记
    • Vue3学习笔记
    • React学习笔记
  • 设计模式
  • 大数据

    • 概览
    • Hadoop
    • Hive
  • 机器学习

    • 机器学习概览
  • openclaw实战
  • claudecode实战
  • RAG
  • 拟人类Agent
  • linux命令速查
  • windows命令速查
  • Docker笔记
  • kubernetes学习笔记
  • kubernetes实操笔记
  • 运维工具大全
  • git操作宝典
  • 概率论
  • 线性代数
  • 统计学
  • 金融知识学习
  • 聚宽
  • 因子分析
  • 后端

    • JAVA基础
    • JAVA多线程
    • JVM
    • 分布式相关
    • 数据库
  • 前端

    • HTML
    • CSS
    • JAVASCRIPT
    • VUE3
    • 网络
    • 前端工程化
    • nodejs
  • AI

    • RAG
  • 健身

    • 笔记
    • 训练计划
  • 读书笔记

    • 《深度学习》
  • 其他

    • RSS
    • 资源导航
    • 医保
    • 装修攻略
我也想搭建这样的博客!
🚋开往
  • React

    • 概述
    • React 基础
    • React Hooks
    • React Router
    • Redux Toolkit
    • Ant Design

Ant Design

阿里巴巴出品的企业级 React UI 组件库,是国内中后台项目的事实标准。
当前主流版本为 antd v5(2022年底发布),基于 CSS-in-JS,彻底移除了 Less 依赖。
官网:https://ant.design/index-cn

安装与接入

安装

npm install antd

接入方式对比

方式特点适用场景
直接使用(v5 推荐)v5 内置按需加载,无需额外配置所有项目
完整引入样式(v4 及以下)需手动引入 antd/dist/antd.cssv4 老项目
按需加载(v4 借助 babel-plugin)减小打包体积v4 项目优化

v5 接入(推荐)

v5 默认使用 CSS-in-JS,无需引入任何 CSS 文件,直接使用即可:

// main.jsx(或 index.jsx)
import React from 'react'
import ReactDOM from 'react-dom/client'
import App from './App'

ReactDOM.createRoot(document.getElementById('root')).render(
  <React.StrictMode>
    <App />
  </React.StrictMode>
)
// App.jsx
import { Button } from 'antd'

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

v5 引入即用,Tree-shaking 自动处理,打包只包含用到的组件。


国际化(中文)

antd 默认英文(日期选择器、分页文案等),切换为中文:

// App.jsx 或顶层组件
import { ConfigProvider } from 'antd'
import zhCN from 'antd/locale/zh_CN'
import dayjs from 'dayjs'
import 'dayjs/locale/zh-cn'

dayjs.locale('zh-cn')  // dayjs 也设为中文,日期组件需要

export default function App() {
  return (
    <ConfigProvider locale={zhCN}>
      {/* 所有子组件都生效 */}
      <YourRouterOrLayout />
    </ConfigProvider>
  )
}

主题定制(v5)

v5 通过 ConfigProvider 的 theme prop 定制设计 Token,无需修改 Less 变量:

import { ConfigProvider, Button } from 'antd'

export default function App() {
  return (
    <ConfigProvider
      theme={{
        token: {
          colorPrimary: '#00b96b',    // 主色
          borderRadius: 4,            // 全局圆角
          colorBgContainer: '#f6ffed', // 容器背景色
        },
        components: {
          Button: {
            colorPrimary: '#ff4d4f',  // 单独覆盖 Button 的主色
          },
        },
      }}
    >
      <Button type="primary">定制主题按钮</Button>
    </ConfigProvider>
  )
}

核心概念

组件命名规则

用法说明
import { Button } from 'antd'所有组件均具名导出,PascalCase 命名
<Button type="primary">props 驱动样式,不依赖 className
Button.Group、Form.Item子组件通过点语法访问

ConfigProvider

ConfigProvider 是 antd 的全局配置组件,一般放在应用最外层,统一控制:

配置项作用
locale国际化语言
theme主题 Token
size全局组件尺寸(large / middle / small)
prefixCls组件 CSS 前缀(默认 ant,微前端时避免冲突)
componentSize同 size

设计 Token(v5 新概念)

v5 将样式变量分为两层:

层级说明示例
Global Token全局设计变量,影响所有组件colorPrimary、borderRadius
Component Token单个组件的专属变量Button.colorPrimary

常用 Global Token:

{
  colorPrimary: '#1677ff',       // 主色
  colorSuccess: '#52c41a',       // 成功色
  colorWarning: '#faad14',       // 警告色
  colorError: '#ff4d4f',         // 错误色
  colorTextBase: '#000',         // 基础文字色
  borderRadius: 6,               // 全局圆角
  fontSize: 14,                  // 基础字号
  wireframe: false,              // 线框模式(true 时所有组件改为线框风格)
}

常用组件

Button 按钮

import { Button, Space, Flex } from 'antd'
import { SearchOutlined, DownloadOutlined } from '@ant-design/icons'

export default function Demo() {
  return (
    <Flex gap="small" wrap>
      {/* type: default / primary / dashed / text / link */}
      <Button>默认</Button>
      <Button type="primary">主要</Button>
      <Button type="dashed">虚线</Button>
      <Button type="text">文本</Button>
      <Button type="link">链接</Button>

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

      {/* 图标 */}
      <Button type="primary" icon={<SearchOutlined />}>搜索</Button>
      <Button icon={<DownloadOutlined />} />

      {/* 加载 / 禁用 */}
      <Button type="primary" loading>提交中</Button>
      <Button disabled>禁用</Button>

      {/* 尺寸 */}
      <Button size="large">大</Button>
      <Button size="small">小</Button>
    </Flex>
  )
}

Input 输入框

import { Input, Space } from 'antd'
import { SearchOutlined, UserOutlined } from '@ant-design/icons'
import { useState } from 'react'

export default function Demo() {
  const [value, setValue] = useState('')

  return (
    <Space direction="vertical" style={{ width: '100%' }}>
      <Input
        placeholder="基础输入框"
        value={value}
        onChange={e => setValue(e.target.value)}
        allowClear
      />

      {/* 前缀 / 后缀图标 */}
      <Input prefix={<UserOutlined />} placeholder="用户名" />
      <Input suffix={<SearchOutlined />} placeholder="搜索" />

      {/* 密码框 */}
      <Input.Password placeholder="请输入密码" />

      {/* 文本域 */}
      <Input.TextArea rows={4} placeholder="请输入描述" maxLength={200} showCount />

      {/* 搜索框(带按钮) */}
      <Input.Search
        placeholder="输入关键词"
        enterButton="搜索"
        onSearch={val => console.log('搜索:', val)}
      />
    </Space>
  )
}

Select 选择器

import { Select, Space } from 'antd'
import { useState } from 'react'

const options = [
  { value: 'beijing', label: '北京' },
  { value: 'shanghai', label: '上海' },
  { value: 'guangzhou', label: '广州' },
]

export default function Demo() {
  const [city, setCity] = useState(null)
  const [cities, setCities] = useState([])

  return (
    <Space direction="vertical" style={{ width: 300 }}>
      {/* 基础 */}
      <Select
        value={city}
        onChange={setCity}
        options={options}
        placeholder="请选择城市"
        allowClear
        style={{ width: '100%' }}
      />

      {/* 多选 */}
      <Select
        mode="multiple"
        value={cities}
        onChange={setCities}
        options={options}
        placeholder="可多选"
        style={{ width: '100%' }}
      />

      {/* 可搜索 */}
      <Select
        showSearch
        options={options}
        placeholder="输入搜索"
        filterOption={(input, option) =>
          option.label.toLowerCase().includes(input.toLowerCase())
        }
        style={{ width: '100%' }}
      />
    </Space>
  )
}

Form 表单

antd Form 基于 rc-field-form,使用 Form.useForm() 获取实例,rules 支持内置校验器和自定义校验函数。

import { Form, Input, Button, Select, Radio, Checkbox, message } from 'antd'

export default function Demo() {
  const [form] = Form.useForm()

  const onFinish = (values) => {
    console.log('表单提交:', values)
    message.success('提交成功')
  }

  const onFinishFailed = ({ errorFields }) => {
    console.log('校验失败:', errorFields)
  }

  return (
    <Form
      form={form}
      labelCol={{ span: 4 }}       // label 占 4 列
      wrapperCol={{ span: 16 }}    // 控件占 16 列
      onFinish={onFinish}
      onFinishFailed={onFinishFailed}
      autoComplete="off"
    >
      <Form.Item
        label="用户名"
        name="username"
        rules={[
          { required: true, message: '请输入用户名' },
          { min: 3, max: 16, message: '长度在 3~16 个字符' },
        ]}
      >
        <Input placeholder="请输入用户名" />
      </Form.Item>

      <Form.Item
        label="邮箱"
        name="email"
        rules={[
          { required: true, message: '请输入邮箱' },
          { type: 'email', message: '邮箱格式不正确' },
        ]}
      >
        <Input />
      </Form.Item>

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

      <Form.Item
        label="确认密码"
        name="confirmPassword"
        dependencies={['password']}   // 依赖 password 字段,password 变化时重新校验
        rules={[
          { required: true, message: '请确认密码' },
          ({ getFieldValue }) => ({
            validator(_, value) {
              if (!value || getFieldValue('password') === value) {
                return Promise.resolve()
              }
              return Promise.reject(new Error('两次密码不一致'))
            },
          }),
        ]}
      >
        <Input.Password />
      </Form.Item>

      <Form.Item label="城市" name="city">
        <Select
          options={[
            { value: 'bj', label: '北京' },
            { value: 'sh', label: '上海' },
          ]}
          placeholder="请选择"
        />
      </Form.Item>

      <Form.Item label="性别" name="gender">
        <Radio.Group>
          <Radio value="male">男</Radio>
          <Radio value="female">女</Radio>
        </Radio.Group>
      </Form.Item>

      <Form.Item label="爱好" name="hobbies">
        <Checkbox.Group
          options={[
            { label: '阅读', value: 'reading' },
            { label: '编程', value: 'coding' },
            { label: '游戏', value: 'gaming' },
          ]}
        />
      </Form.Item>

      <Form.Item wrapperCol={{ offset: 4 }}>
        <Button type="primary" htmlType="submit">提交</Button>
        <Button style={{ marginLeft: 8 }} onClick={() => form.resetFields()}>重置</Button>
      </Form.Item>
    </Form>
  )
}

Table 表格

import { Table, Button, Space, Tag, Popconfirm, message } from 'antd'
import { useState } from 'react'

const initialData = [
  { key: '1', name: '张三', age: 28, status: 'active', address: '北京市朝阳区' },
  { key: '2', name: '李四', age: 32, status: 'inactive', address: '上海市浦东新区' },
  { key: '3', name: '王五', age: 25, status: 'active', address: '广州市天河区' },
]

export default function Demo() {
  const [data, setData] = useState(initialData)
  const [selectedKeys, setSelectedKeys] = useState([])
  const [loading, setLoading] = useState(false)

  const columns = [
    {
      title: '姓名',
      dataIndex: 'name',
      sorter: (a, b) => a.name.localeCompare(b.name),
    },
    {
      title: '年龄',
      dataIndex: 'age',
      sorter: (a, b) => a.age - b.age,
    },
    {
      title: '状态',
      dataIndex: 'status',
      filters: [
        { text: '启用', value: 'active' },
        { text: '禁用', value: 'inactive' },
      ],
      onFilter: (value, record) => record.status === value,
      render: (status) => (
        <Tag color={status === 'active' ? 'green' : 'red'}>
          {status === 'active' ? '启用' : '禁用'}
        </Tag>
      ),
    },
    {
      title: '地址',
      dataIndex: 'address',
      ellipsis: true,   // 超长省略号
    },
    {
      title: '操作',
      key: 'action',
      render: (_, record) => (
        <Space>
          <Button type="link" size="small" onClick={() => handleEdit(record)}>编辑</Button>
          <Popconfirm
            title="确定删除吗?"
            onConfirm={() => handleDelete(record.key)}
            okText="确定"
            cancelText="取消"
          >
            <Button type="link" size="small" danger>删除</Button>
          </Popconfirm>
        </Space>
      ),
    },
  ]

  const handleEdit = (record) => {
    console.log('编辑:', record)
  }

  const handleDelete = (key) => {
    setData(data.filter(item => item.key !== key))
    message.success('删除成功')
  }

  const rowSelection = {
    selectedRowKeys: selectedKeys,
    onChange: setSelectedKeys,
  }

  return (
    <>
      {selectedKeys.length > 0 && (
        <div style={{ marginBottom: 8 }}>
          已选 {selectedKeys.length} 条
          <Button type="link" onClick={() => setSelectedKeys([])}>取消选择</Button>
        </div>
      )}
      <Table
        rowSelection={rowSelection}
        columns={columns}
        dataSource={data}
        loading={loading}
        pagination={{ pageSize: 10, showTotal: total => `共 ${total} 条` }}
        bordered
      />
    </>
  )
}

Modal 对话框

import { Modal, Form, Input, Button, message } from 'antd'
import { useState } from 'react'

export default function Demo() {
  const [open, setOpen] = useState(false)
  const [confirmLoading, setConfirmLoading] = useState(false)
  const [form] = Form.useForm()

  const handleOk = async () => {
    try {
      const values = await form.validateFields()   // 校验并获取值
      setConfirmLoading(true)
      // 模拟接口请求
      await new Promise(r => setTimeout(r, 1000))
      console.log('提交:', values)
      message.success('操作成功')
      setOpen(false)
      form.resetFields()
    } catch {
      // 校验失败,不处理(antd 会自动显示错误信息)
    } finally {
      setConfirmLoading(false)
    }
  }

  const handleCancel = () => {
    setOpen(false)
    form.resetFields()
  }

  return (
    <>
      <Button type="primary" onClick={() => setOpen(true)}>新增用户</Button>

      <Modal
        title="新增用户"
        open={open}
        onOk={handleOk}
        onCancel={handleCancel}
        confirmLoading={confirmLoading}
        maskClosable={false}      // 点击遮罩不关闭
        destroyOnClose            // 关闭时销毁内容,下次打开重置状态
      >
        <Form form={form} labelCol={{ span: 4 }} style={{ marginTop: 24 }}>
          <Form.Item label="姓名" name="name" rules={[{ required: true }]}>
            <Input />
          </Form.Item>
          <Form.Item label="邮箱" name="email" rules={[{ type: 'email' }]}>
            <Input />
          </Form.Item>
        </Form>
      </Modal>
    </>
  )
}

Message / Notification / Modal.confirm 消息提示

import { message, notification, Modal, Button, Space } from 'antd'

export default function Demo() {
  // ① message:顶部轻提示,自动消失
  const showMessage = () => {
    message.success('操作成功')
    message.error('操作失败')
    message.warning('请注意')
    message.info('提示信息')
    message.loading('加载中...', 2)  // 2秒后消失
  }

  // ② notification:右上角通知,停留时间较长
  const showNotification = () => {
    notification.open({
      message: '新消息',
      description: '您有一条新的系统通知,请及时处理。',
      type: 'info',
      duration: 4.5,
      placement: 'topRight',
    })
  }

  // ③ Modal.confirm:确认弹框
  const showConfirm = () => {
    Modal.confirm({
      title: '确认删除',
      content: '删除后无法恢复,是否继续?',
      okText: '确认删除',
      okType: 'danger',
      cancelText: '取消',
      onOk() {
        message.success('已删除')
      },
    })
  }

  return (
    <Space>
      <Button onClick={showMessage}>Message</Button>
      <Button onClick={showNotification}>Notification</Button>
      <Button danger onClick={showConfirm}>Modal.confirm</Button>
    </Space>
  )
}

DatePicker 日期选择

import { DatePicker, Space } from 'antd'
import dayjs from 'dayjs'
import { useState } from 'react'

export default function Demo() {
  const [date, setDate] = useState(null)
  const [range, setRange] = useState([null, null])

  return (
    <Space direction="vertical" size={12}>
      {/* 单日期 */}
      <DatePicker
        value={date}
        onChange={setDate}
        format="YYYY-MM-DD"
        placeholder="选择日期"
      />

      {/* 日期范围 */}
      <DatePicker.RangePicker
        value={range}
        onChange={setRange}
        format="YYYY-MM-DD"
      />

      {/* 禁用历史日期 */}
      <DatePicker
        disabledDate={current => current && current < dayjs().startOf('day')}
        placeholder="只能选今天及以后"
      />
    </Space>
  )
}

antd v5 的日期组件依赖 dayjs(替代了 v4 的 moment.js),需安装:npm install dayjs


Upload 文件上传

import { Upload, Button, message } from 'antd'
import { UploadOutlined, InboxOutlined } from '@ant-design/icons'
import { useState } from 'react'

export default function Demo() {
  const [fileList, setFileList] = useState([])

  const uploadProps = {
    name: 'file',
    action: 'https://your-api.com/upload',
    headers: { Authorization: 'Bearer your-token' },
    fileList,
    onChange({ file, fileList }) {
      setFileList(fileList)
      if (file.status === 'done') {
        message.success(`${file.name} 上传成功`)
      } else if (file.status === 'error') {
        message.error(`${file.name} 上传失败`)
      }
    },
    beforeUpload(file) {
      const isLt10M = file.size / 1024 / 1024 < 10
      if (!isLt10M) {
        message.error('文件不能超过 10MB')
        return Upload.LIST_IGNORE   // 阻止上传但不添加到列表
      }
      return true
    },
    maxCount: 3,
    accept: '.jpg,.png,.pdf',
  }

  return (
    <>
      {/* 按钮式上传 */}
      <Upload {...uploadProps}>
        <Button icon={<UploadOutlined />}>点击上传</Button>
      </Upload>

      {/* 拖拽上传 */}
      <Upload.Dragger {...uploadProps} style={{ marginTop: 16 }}>
        <p><InboxOutlined style={{ fontSize: 48, color: '#1677ff' }} /></p>
        <p>点击或拖拽文件到此区域上传</p>
        <p style={{ color: '#999' }}>支持 jpg / png / pdf,单文件不超过 10MB</p>
      </Upload.Dragger>
    </>
  )
}

Menu 导航菜单

import { Menu, Layout } from 'antd'
import {
  HomeOutlined,
  UserOutlined,
  SettingOutlined,
  TeamOutlined,
} from '@ant-design/icons'
import { useState } from 'react'
import { useNavigate, useLocation } from 'react-router-dom'

const menuItems = [
  { key: '/', icon: <HomeOutlined />, label: '首页' },
  {
    key: 'user',
    icon: <UserOutlined />,
    label: '用户管理',
    children: [
      { key: '/user/list', icon: <TeamOutlined />, label: '用户列表' },
      { key: '/user/role', label: '角色管理' },
    ],
  },
  { key: '/setting', icon: <SettingOutlined />, label: '系统设置' },
]

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

  return (
    <Layout.Sider width={220}>
      <Menu
        mode="inline"
        selectedKeys={[location.pathname]}   // 根据当前路由高亮菜单项
        defaultOpenKeys={['user']}
        items={menuItems}
        onClick={({ key }) => navigate(key)}  // 点击跳转路由
        style={{ height: '100%' }}
      />
    </Layout.Sider>
  )
}

图标使用

antd 的图标独立为 @ant-design/icons 包:

npm install @ant-design/icons
import {
  SearchOutlined,   // Outlined:线框风格(最常用)
  SearchFilled,     // Filled:填充风格
  SearchTwoTone,    // TwoTone:双色风格
  LoadingOutlined,
} from '@ant-design/icons'
import { Spin } from 'antd'

export default function Demo() {
  return (
    <div>
      <SearchOutlined style={{ fontSize: 20, color: '#1677ff' }} />
      <SearchFilled style={{ fontSize: 20 }} />
      <SearchTwoTone twoToneColor="#eb2f96" style={{ fontSize: 20 }} />

      {/* 自定义 Loading 图标 */}
      <Spin indicator={<LoadingOutlined style={{ fontSize: 24 }} spin />} />
    </div>
  )
}

布局系统

Grid 栅格(24列)

import { Row, Col, Divider } from 'antd'

export default function Demo() {
  const style = { background: '#0958d9', color: '#fff', padding: '8px 0', textAlign: 'center' }

  return (
    <>
      {/* gutter:列间距,[水平, 垂直] */}
      <Row gutter={[16, 16]}>
        <Col span={6}><div style={style}>6</div></Col>
        <Col span={12}><div style={style}>12</div></Col>
        <Col span={6}><div style={style}>6</div></Col>
      </Row>

      {/* 响应式:xs(<576) / sm(≥576) / md(≥768) / lg(≥992) / xl(≥1200) / xxl(≥1600) */}
      <Row gutter={16} style={{ marginTop: 16 }}>
        <Col xs={24} sm={12} md={8} lg={6}>
          <div style={style}>响应式列</div>
        </Col>
      </Row>
    </>
  )
}

Space 间距

import { Space, Button } from 'antd'

export default function Demo() {
  return (
    // size: 'small' | 'middle' | 'large' | number
    // wrap: 允许换行
    <Space size="middle" wrap>
      <Button>按钮1</Button>
      <Button>按钮2</Button>
      <Button>按钮3</Button>
    </Space>
  )
}

Layout 页面布局

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

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

export default function BasicLayout({ children }) {
  const { token } = theme.useToken()  // 获取当前主题 Token

  return (
    <Layout style={{ minHeight: '100vh' }}>
      <Sider>
        {/* 侧边导航 */}
        <div style={{ height: 32, margin: 16, background: 'rgba(255,255,255,.2)' }} />
        <Menu theme="dark" mode="inline" items={[]} />
      </Sider>

      <Layout>
        <Header style={{ background: token.colorBgContainer, padding: '0 24px' }}>
          {/* 顶部导航栏 */}
        </Header>

        <Content style={{ margin: '24px 16px', padding: 24, background: token.colorBgContainer }}>
          {children}
        </Content>

        <Footer style={{ textAlign: 'center' }}>
          My App ©2024
        </Footer>
      </Layout>
    </Layout>
  )
}

与 React Hook Form / Zustand 搭配

实际项目中,antd Form 可以独立使用;如果状态管理需要跨组件共享表单数据,可搭配 Zustand:

// 搭配 Zustand 存储表单草稿
import { create } from 'zustand'

const useFormStore = create((set) => ({
  draft: {},
  setDraft: (values) => set({ draft: values }),
  clearDraft: () => set({ draft: {} }),
}))

// 在表单组件中
const { draft, setDraft } = useFormStore()

// onValuesChange 实时保存草稿
<Form initialValues={draft} onValuesChange={(_, all) => setDraft(all)}>
  ...
</Form>

在 Axios 中配合全局 Loading

// request.js
import axios from 'axios'
import { message } from 'antd'

const request = axios.create({
  baseURL: '/api',
  timeout: 10000,
})

// 请求拦截:附加 token
request.interceptors.request.use((config) => {
  const token = localStorage.getItem('token')
  if (token) config.headers.Authorization = `Bearer ${token}`
  return config
})

// 响应拦截:统一错误提示
request.interceptors.response.use(
  (response) => response.data,
  (error) => {
    const status = error.response?.status
    const msg = {
      400: '请求参数错误',
      401: '未登录,请先登录',
      403: '无权限访问',
      404: '资源不存在',
      500: '服务器内部错误',
    }[status] || error.response?.data?.message || '网络错误'

    message.error(msg)

    // 401 时跳转登录页
    if (status === 401) {
      window.location.href = '/login'
    }

    return Promise.reject(error)
  }
)

export default request

FAQ

Q:v5 和 v4 最大的区别是什么?
A:v5 用 CSS-in-JS 替代 Less,无需引入 CSS 文件,支持动态主题,但 SSR 需要额外配置;日期组件从 moment.js 改为 dayjs,体积大幅减小。

Q:message / notification 在函数组件外(如 axios 拦截器)能用吗?
A:可以直接 import { message } from 'antd' 调用静态方法,不依赖 React 上下文。但 v5 推荐在组件内用 message.useMessage() hook,以便正确继承 ConfigProvider 的配置(如主题、语言)。

Q:Table 的 dataSource 必须有 key 字段吗?
A:必须保证每行数据唯一标识,可以是 key 字段,也可以在 Table 上设置 rowKey="id" 指定其他字段。

Q:Form 的 initialValues 和 form.setFieldsValue 有什么区别?
A:initialValues 只在组件首次挂载时生效,后续变化不会更新;form.setFieldsValue(values) 可在任意时刻动态填充表单(如编辑弹框回显数据)。

Q:Modal 关闭后表单数据没清空?
A:在 Modal 上加 destroyOnClose 可在关闭时销毁内容;或在 onCancel / afterClose 中调用 form.resetFields()。两者都有效,destroyOnClose 更彻底但每次打开都重新挂载。

Q:antd 组件样式被全局 CSS 污染怎么办?
A:v5 支持 CSS 隔离,在 ConfigProvider 上配置 prefixCls 修改前缀;或开启 { theme: { cssVar: true } } 使用 CSS 变量模式,减少样式冲突。

Last Updated: 7/1/26, 7:02 AM
Contributors: dongyz8
Prev
Redux Toolkit