Ant Design 6 入门
面向 VOZEB PRO 项目,讲解 Ant Design 6 是什么、在本仓库怎么接入、常用写法和什么时候该用它。
0. 什么时候使用 Ant Design?
适合使用 Ant Design 的场景:
- ✅ 管理后台 SaaS 控件 — 表格、表单、分页、抽屉、日期选择、数字输入、开关、标签。本项目后台几乎全靠这一套
- ✅ 标准交互 overlay —
Modal、Drawer、Dropdown、Popover、Popconfirm、Tooltip。键盘、焦点、遮罩、滚动锁定都已处理好 - ✅ 需要统一反馈 — 成功/失败提示、确认框。必须走
App.useApp(),不要自己alert或再引一套 Toast - ✅ 需要主题一致 — 浅色/深色、主色、圆角、表格选中色已在
getAntThemeConfig()配好,用 antd 组件会自动跟上 - ✅ 密集表单/列表工作流 — 后台约定是「列表 + 创建按钮 + Modal/Drawer 表单」,antd 的
Table+Form+Modal就是为这个设计的 - ✅ 中文 locale — 分页、空态、日期选择已经是中文,不用自己翻译
不建议使用(或需要权衡)的场景:
- ⚠️ 页面骨架和间距 — 用 Tailwind:
flex、grid、gap、px-*、max-w-*。不要用 antd 的Row/Col/Layout再套一层栅格 - ⚠️ 高度定制的创作界面 — Canvas 节点、工作台结果墙、瀑布流卡片,交互和视觉都是项目自研,只在局部借用
Button/Modal/Switch - ⚠️ antd 已经有等价物时再引 shadcn — 同一功能不要两套组件各写一遍。本项目 shadcn 只是补充(例如已有的
@/components/ui/select.tsx) - ⚠️ 在 Server Component 里直接 import antd 组件 — antd 组件依赖客户端状态和 Context,必须写在
"use client"文件里 - ⚠️ 用 Tailwind 大面积覆盖 antd 内部 class — 能改主题 token 就改 token;
!py-2这种选择器只作局部微调 - ⚠️ 自己写
dark ? ...去改 antd 组件颜色 — 后台主题统一走app-theme.ts/AppProviders,页面里不要再分叉
在 VOZEB PRO 中的定位: Ant Design 是主体 UI 库。Tailwind 管布局和微调,shadcn/Radix 只补 antd 不方便的点。新需求先问「antd 有没有现成组件」,有就用,没有再考虑手写或 shadcn。
1. 什么是 Ant Design 6
Ant Design(简称 antd)是一套带完整样式和交互的企业级 React 组件库。和 shadcn 不同,它是真正的 npm 依赖:安装后直接 import { Button } from "antd",不把源码复制进仓库。
本项目锁定:
| 包 | 版本(扫描时) | 作用 |
|---|---|---|
antd |
^6.5.3 |
组件本体 |
@ant-design/nextjs-registry |
^1.3.0 |
Next.js App Router 下的样式注入,避免 SSR 闪烁 |
antd/locale/zh_CN |
随 antd | 中文文案 |
dayjs |
^1.11.20 |
DatePicker 的日期库,locale 用 zh-cn |
v6 相对旧版,对你写业务代码影响最大的几点:
- 主题继续走
ConfigProvider的token/components,本项目还开了cssVar - 全局提示、对话框必须挂在
<App>下,用App.useApp()取message/modal/notification - 和 Next.js App Router 搭配时,根布局要用
AntdRegistry包一层
它解决的是「表格怎么筛、表单怎么校验、抽屉怎么在手机上别撑破屏幕」这类产品控件问题,不是「这一行怎么 flex 居中」。
2. 和 Tailwind、shadcn 怎么分工
页面结构 / 间距 / 响应式 → Tailwind
按钮、表单、表格、弹层、提示 → Ant Design
antd 没有、又要可改源码 → shadcn(少用)
图标 → lucide-react(不要用 @ant-design/icons 另起一套)
对照:
| 维度 | Ant Design | Tailwind | shadcn/ui |
|---|---|---|---|
| 提供什么 | 现成控件 + 交互 | 原子 class | 复制进仓库的控件源码 |
| 主题 | ConfigProvider token |
@theme / dark: |
CSS 变量 + Tailwind |
| 本项目用量 | 约 157 个文件直接 import | 几乎所有页面 | 仅 components/ui 里少量 |
| 改外观 | 改 app-theme.ts |
改 class | 改组件源码 |
| 典型场景 | 后台表格、支付表单、确认框 | 工作区壳、卡片网格 | 个别定制 Select |
一个真实组合(「我的提示词」页):外层 div 用 Tailwind 排版,里面的 Table / Modal / Form / Popconfirm / Button 用 antd。
3. 本项目是怎么接入的
链路从上到下只有三层,新页面不要再包一套 ConfigProvider。
3.1 根布局:注入样式
web/src/app/layout.tsx:
import { AntdRegistry } from "@ant-design/nextjs-registry";
import { AppProviders } from "@/components/layout/app-providers";
import "antd/dist/reset.css";
<AntdRegistry>
<AppProviders>{children}</AppProviders>
</AntdRegistry>
antd/dist/reset.css:清掉浏览器默认,和 antd 控件对齐AntdRegistry:把 antd 的 CSS-in-JS 抽到 SSR HTML 里,避免首屏样式跳动
3.2 AppProviders:主题、中文、全局 App
web/src/components/layout/app-providers.tsx:
<ConfigProvider locale={zhCN} theme={getAntThemeConfig(dark)}>
<App message={{ top: 84, duration: 2.4, maxCount: 3 }}>
<QueryClientProvider client={queryClient}>
<ClientRootInit>{children}</ClientRootInit>
</QueryClientProvider>
</App>
</ConfigProvider>
同时做了三件事:
locale={zhCN}+dayjs.locale("zh-cn")— 控件中文theme={getAntThemeConfig(dark)}— 跟 Zustand 的主题 store 同步浅色/深色<App>— 给全站message/modal提供上下文;top: 84是为了避开顶部导航
3.3 主题只改一处
web/src/lib/app-theme.ts 的 getAntThemeConfig(dark) 集中写了:
- 算法:
defaultAlgorithm/darkAlgorithm cssVar.key:vozeb-pro-light/vozeb-pro-dark- 全局 token:主色、背景、边框、文字、圆角 8
- 组件级:
Button、Menu、Select、Cascader、TreeSelect、Table
主色是中性黑/白,不是 antd 默认蓝。以后要改品牌色,改这个文件,不要在页面里写死 #1677ff。
4. 最小用法
antd 组件必须放在客户端组件里。
"use client";
import { App, Button } from "antd";
export function SaveButton() {
const { message } = App.useApp();
return (
<Button
type="primary"
onClick={() => message.success("已保存")}
>
保存
</Button>
);
}
要点:
- 文件第一行
"use client" - 提示用
App.useApp().message,不要import { message } from "antd"再静态调用(在 App Router 里会丢上下文、主题也对不上) - 主操作按钮用
type="primary",删除用danger - 图标用 lucide,通过
icon={<Plus className="size-4" />}传入
5. 本项目里最常见的五种写法
5.1 反馈:复制、成功、失败
全局 hook 已经包好了,重复动作不要再手写一遍。
import { useCopyText } from "@/hooks/use-copy-text";
const copyText = useCopyText();
copyText(record.prompt, "提示词已复制");
useCopyText 内部就是 App.useApp().message。下载、确认框同类副作用,优先抽到 web/src/hooks/。
业务失败则:
const { message } = App.useApp();
try {
await createMyPrompt(value);
message.success("提示词已保存");
} catch (error) {
message.error(error instanceof Error ? error.message : "新增提示词失败");
}
5.2 列表页:Table + Modal + Form
后台和「我的提示词」都是这个骨架。
"use client";
import { App, Button, Form, Input, Modal, Popconfirm, Table } from "antd";
import type { TableColumnsType } from "antd";
type PromptFormValue = { title: string; prompt: string };
export function ExampleList() {
const { message } = App.useApp();
const [form] = Form.useForm<PromptFormValue>();
const [open, setOpen] = useState(false);
const columns: TableColumnsType<Item> = [
{ title: "标题", dataIndex: "title" },
{
title: "操作",
render: (_, record) => (
<Popconfirm title="删除?" okText="删除" cancelText="取消" onConfirm={() => onDelete(record.id)}>
<Button size="small" danger>
删除
</Button>
</Popconfirm>
),
},
];
return (
<>
<Button type="primary" onClick={() => setOpen(true)}>
添加
</Button>
<Table rowKey="id" columns={columns} dataSource={items} pagination={false} />
<Modal title="添加提示词" open={open} onCancel={() => setOpen(false)} footer={null} destroyOnHidden>
<Form form={form} layout="vertical" onFinish={onCreate}>
<Form.Item name="title" label="标题" rules={[{ required: true, message: "请填写标题" }]}>
<Input />
</Form.Item>
<Form.Item name="prompt" label="内容" rules={[{ required: true }]}>
<Input.TextArea rows={6} />
</Form.Item>
<Button type="primary" htmlType="submit">
保存
</Button>
</Form>
</Modal>
</>
);
}
约定:
- 大表单不要常驻铺在页面主体,放进 Modal / Drawer
destroyOnHidden(或项目里已有的destroyOnHidden)避免关掉后残留校验状态- 表格列类型用
TableColumnsType<T>,不要写成any - 短字段用网格铺满一行,密钥/长文本单独通栏(见
AGENTS.md后台表单规则)
5.3 抽屉:宽度必须响应式
不要写 size="large",手机上会撑出横向滚动。
<Drawer
title={channel.name || "渠道详情"}
width="min(720px, 100vw)"
open={open}
destroyOnHidden
onClose={onClose}
>
...
</Drawer>
AdminChannelDetailDrawer、MobileNavDrawer 都是这个模式。改完要在 390px / 430px 看抽屉根节点和底部按钮有没有被裁切。
5.4 受控开关、选择,只借控件不借布局
工作台设置面板经常只要 antd 的交互,外壳仍是 Tailwind:
import { Switch } from "antd";
<Switch checked={enabled} onChange={setEnabled} />
image-settings-panel.tsx 甚至会再包一层局部 ConfigProvider,只为了让某个 Switch 跟画布主题走。这是例外,默认仍用根上的主题。
5.5 确认删除:Popconfirm 或 App.modal
单行危险操作:
<Popconfirm title="删除提示词?" okText="删除" cancelText="取消" onConfirm={() => deletePrompt(id)}>
<Button size="small" danger />
</Popconfirm>
需要动态文案、异步逻辑时,用 App.useApp().modal.confirm(...)(促销活动等后台页是这样)。
6. 本项目高频组件速查
按「先想场景再找组件」,不要先翻官方 80 个组件。
| 你想做的事 | 用这个 | 项目里能对照的地方 |
|---|---|---|
| 主按钮 / 次按钮 / 危险按钮 | Button |
几乎所有页 |
| 单行、密码、多行输入 | Input / Input.Password / Input.TextArea |
auth-form.tsx |
| 数字、金额 | InputNumber |
促销、计费后台 |
| 下拉选择 | Select |
画廊筛选、后台筛选项 |
| 开关 | Switch |
图片/视频设置、促销启用 |
| 分段切换 | Segmented |
后台列表视图切换 |
| 日期范围 | DatePicker / DatePicker.RangePicker |
促销活动 |
| 标签 | Tag |
能力、状态、协议 |
| 表格 | Table |
我的提示词、对账、作品治理 |
| 分页(表格外) | Pagination |
素材库、作品、促销 |
| 表单校验 | Form + Form.useForm |
我的提示词、促销、优惠券 |
| 弹窗表单 | Modal |
后台创建/编辑 |
| 侧栏详情 | Drawer |
渠道详情、移动导航 |
| 轻确认 | Popconfirm |
行内删除 |
| 轻提示 | App.useApp().message |
登录、保存、复制 |
| 空数据 | Empty |
提示词选择弹窗 |
| 加载 | Spin |
列表请求中 |
| 下拉菜单 | Dropdown |
顶栏用户操作 |
| 气泡 | Popover / Tooltip |
画布工具、简短说明 |
| 时间线 | Timeline |
版本发布说明 |
用不到就别引。Canvas 主画布、瀑布流、对话气泡都不是 antd 的活。
7. 决策清单:这一处该不该上 antd
写 UI 前按顺序问:
- 这是后台列表/表单/筛选/弹层吗? → 用 antd,并沿用「列表 + Modal/Drawer」。
- 这是全站都要一致的轻反馈吗?(复制成功、保存失败) →
App.useApp()或已有 hook。 - 这只是排版(宽、高、间距、两栏、粘性顶栏)吗? → Tailwind,不要
Row/Col。 - 这是创作画布、结果墙、对话流、自定义节点吗? → 自研结构 + 必要时借一两个 antd 控件。
- antd 没有、交互还很特殊、又希望拥有源码? → 才考虑 shadcn。
- 能不能改
app-theme.ts解决外观? → 能就不要在 JSX 里堆className="[&_.ant-xxx]"。
反例:
- 用 antd
Card再包一层只为了阴影 — 用 Tailwindrounded-lg border bg-card即可,项目里大量卡片是这样 - 用 antd
Typography.Title当页面主标题 — 直接h1+ Tailwind,和用户工作区其它页一致 - 为了一个定制下拉再
npx shadcn add select,而页面隔壁已经在用antd的Select
8. 和 Next.js App Router 相处的注意点
- 客户端边界
含 antd 的文件必须"use client"。服务端page.tsx可以渲染一个客户端子组件,不要在 Server Component 里直接写<Table />。 - 不要静态
message.success
v5 以后官方也不推荐静态方法。本项目已经用<App>包住全树,统一App.useApp()。 - 水合
根布局有suppressHydrationWarning,主题 class 在useLayoutEffect里写到<html>。不要在首屏用依赖window的 antd 默认值去渲和服务器不一致的 DOM。 - 包体积
按需import { Button, Table } from "antd"即可,不要import antd from "antd"。项目里已经是具名导入。 - Drawer / Modal 宽度
移动端必须min(..., 100vw),并确认内部滚动容器,不要让 antd 默认固定宽度破坏html, body { overflow: hidden }的工作区壳。 - 覆盖样式的优先级
主题 token > 组件 props(size、variant)> 极少量className。AGENTS.md要求按钮在浅色/深色/hover/disabled 下都可读;主按钮深底必须白字,不要用 Tailwind 把type="primary"改回黑字。
9. 官方文档怎么查
写 antd 代码时,以当前大版本文档为准,并结合本仓库已有写法:
- 组件与 API:https://ant.design/components/overview-cn
- 主题 Token:https://ant.design/docs/react/customize-theme-cn
- App 包裹与静态方法:https://ant.design/components/app-cn
- 给 LLM 用的全量参考(
AGENTS.md指定):https://ant.design/llms-full.txt
查 API 时看 antd 6,不要抄 v4 的 Form.create() 或 v3 的 antd.xxx。
10. 相关文档
- Tailwind CSS 4 入门 — 布局、间距、暗色 class
- shadcn/ui 入门 — 仅在 antd 不够用时
web/src/lib/app-theme.ts— 全站 antd 主题web/src/components/layout/app-providers.tsx— ConfigProvider / App 挂载AGENTS.md「前端规范」— 后台信息密度、Drawer 宽度、按钮对比度