UI 框架集成
选讲Tailwind CSS、Ant Design、Element Plus——优雅接入 TS 项目
🎯 学习目标
学完本章,你将能够:
- 能够配置和使用 Tailwind CSS 实现响应式设计与暗黑模式
- 能够集成 Ant Design(React)或 Element Plus(Vue 3)组件库
- 能够使用 Zod 实现类型安全的表单验证
- 能够理解 Headless UI 概念并集成 Shadcn 组件
- 能够定制组件库主题与实现图标系统
你有没有想过
为什么别人的界面好看又统一,你的页面样式东拼西凑?
从头写 CSS 太慢,但直接用 UI 框架又怕类型不支持。好消息是:现代 UI 框架都有完整的 TypeScript 类型定义,你可以在类型安全的保护下使用它们。
本章覆盖三个主题:Tailwind CSS 的原子化样式、Ant Design 和 Element Plus 的组件库集成、图标库的类型安全使用。
Part 1: Tailwind CSS
原子化 CSS 框架,用类名代替写样式。
Tailwind 是目前最流行的原子化 CSS 方案。你不需要写 .card { padding: 16px; },而是直接写 class="p-4"。所有样式都在 HTML 里,改起来一目了然。
本部分从安装配置开始,逐步讲到响应式设计、自定义主题和暗黑模式。学完你就能独立配置一个完整的 Tailwind 项目。
Tailwind 安装
三步接入 Vite + TS 项目
v3。Tailwind v4(2025.1 发布)改为 CSS-first 配置(用 @import "tailwindcss" 和 @theme {} 替代 tailwind.config.ts 和 @tailwind 指令)。为了用上下面讲解的 tailwind.config.ts 工作流,请安装时显式锁定 v3:
$ npm install -D tailwindcss@3 postcss autoprefixer
如果你正在新建项目想直接用 v4,请查阅 Tailwind 官方迁移指南(https://tailwindcss.com/docs/upgrade-guide),v4 的 CSS-first 写法更简洁但配置方式与本章不同。
$ npm install -D tailwindcss@3 postcss autoprefixer
$ npx tailwindcss init -p
初始化后会生成 tailwind.config.js。如果你的项目用 TypeScript,可以改用 .ts 格式:
// tailwind.config.ts
import type { Config } from "tailwindcss";
export default {
content: [
"./index.html",
"./src/**/*.{vue,js,ts,jsx,tsx}",
],
theme: {
extend: {
colors: {
primary: "#0f3460",
accent: "#e94560",
},
},
},
} satisfies Config;
/* src/index.css */
@tailwind base;
@tailwind components;
@tailwind utilities;
Tailwind + React
用类名组合出任何样式,不用写一行 CSS
// 类型安全的 Button 组件
function Button({
children,
variant = "primary",
}: {
children: React.ReactNode;
variant?: "primary" | "secondary";
}) {
const base = "px-4 py-2 rounded font-medium transition-colors";
const variants = {
primary: "bg-primary text-white hover:bg-primary/90",
secondary: "bg-gray-200 text-gray-800 hover:bg-gray-300",
};
return (
<button className={`${base} ${variants[variant]}`}>
{children}
</button>
);
}
用联合类型约束 variant 属性,编辑器会提示可选的值,传错了立刻标红。
Tailwind + Vue
在模板中直接使用 Tailwind 类名
<template>
<button class="px-4 py-2 bg-primary text-white rounded
hover:bg-primary/90 transition-colors">
<slot />
</button>
</template>
Tailwind 的核心理念:用类名描述样式,而不是用 CSS 文件。好处是改样式不用在 HTML 和 CSS 之间跳来跳去,所有信息都在一个地方。
Tailwind 类型扩展
自定义 theme 时,让 TS 也认识你的配置
// tailwind.config.ts —— 使用 satisfies 获得类型检查
import type { Config } from "tailwindcss";
export default {
content: ["./src/**/*.{vue,tsx}"],
theme: {
extend: {
colors: {
primary: "#0f3460",
accent: "#e94560",
},
},
},
} satisfies Config;
// 使用 satisfies 而非 as Config 的好处:
// 编辑器既能自动补全 theme 的所有选项,
// 又能在你写错配置项时报错
响应式设计
一套代码适配手机、平板、桌面
Tailwind 采用移动优先策略:裸类名对应手机,加前缀对应更大屏幕。
断点前缀
sm: 640px
md: 768px
lg: 1024px
xl: 1280px
2xl: 1536px
理解方式
p-2 → 所有屏幕
md:p-4 → 768px 以上
lg:p-6 → 1024px 以上
小屏幕是基础,大屏幕是增强。
// 响应式卡片网格:手机单列,平板两列,桌面三列
function CardGrid({ items }: { items: CardData[] }) {
return (
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
{items.map((item) => (
<div key={item.id} className="p-4 rounded-lg border">
<h3 className="text-base md:text-lg font-bold">
{item.title}
</h3>
<p className="text-sm md:text-base text-gray-600">
{item.desc}
</p>
</div>
))}
</div>
);
}
响应式常用模式
侧边栏折叠、导航切换、容器宽度
// 模式一:侧边栏在大屏展开,小屏隐藏
<div className="flex">
<aside className="hidden lg:block w-64 shrink-0">
侧边栏
</aside>
<main className="flex-1 p-4">
主内容
</main>
</div>
// 模式二:导航栏手机端显示汉堡菜单
<nav>
<div className="flex md:hidden">汉堡按钮</div>
<div className="hidden md:flex gap-4">桌面导航</div>
</nav>
// 模式三:container 类自动居中
<div className="container mx-auto px-4">
内容自动限制宽度并居中
</div>
核心思路:先写手机版样式,再用 md: / lg: 覆盖大屏样式。不要反过来。这样即使忘了加大屏样式,手机版也能正常显示。
md:p-4 写成 p-4 md:p-2,结果大屏幕反而更小。记住 Tailwind 后面的类不会覆盖前面的——它们是不同条件下的规则。md: 只在 768px 以上生效。
自定义主题
让 Tailwind 适配你的设计规范
项目有自己的品牌色、字号和间距。通过 tailwind.config.ts 的 theme.extend 把设计规范注入 Tailwind。
// tailwind.config.ts
import type { Config } from "tailwindcss";
export default {
content: ["./src/**/*.{vue,tsx,jsx}"],
theme: {
extend: {
// 自定义颜色
colors: {
brand: {
50: "#eff6ff",
500: "#3b82f6",
900: "#1e3a5f",
},
},
// 自定义字体
fontFamily: {
sans: ["Inter", "system-ui", "sans-serif"],
mono: ["Fira Code", "monospace"],
},
// 自定义间距
spacing: {
18: "4.5rem", // tailwind 没有的尺寸
88: "22rem",
},
},
},
plugins: [],
} satisfies Config;
自定义工具类
用 plugin 和 CSS 变量建立设计体系
// tailwind.config.ts —— 添加自定义工具类
import type { Config } from "tailwindcss";
import plugin from "tailwindcss/plugin";
export default {
// ...
plugins: [
plugin(function ({ addUtilities }) {
addUtilities({
".text-balance": {
"text-wrap": "balance",
},
".glass": {
"background": "rgba(255,255,255,0.1)",
"backdrop-filter": "blur(10px)",
"border": "1px solid rgba(255,255,255,0.2)",
},
});
}),
],
} satisfies Config;
CSS 变量 + Tailwind
/* index.css */
:root {
--color-brand: #3b82f6;
--color-surface: #ffffff;
}
在 Tailwind 中引用
// tailwind.config.ts
colors: {
brand: "var(--color-brand)",
surface: "var(--color-surface)",
}
用 CSS 变量做 token,Tailwind 做工具类。这样切主题时只需改 CSS 变量,Tailwind 类名不用动。
暗黑模式
dark: 前缀让暗色适配变得简单
class 策略(推荐)
// tailwind.config.ts
export default {
darkMode: "class",
// ...
} satisfies Config;
在 <html> 上加 class="dark" 即可切换。
media 策略
// tailwind.config.ts
export default {
darkMode: "media",
// ...
} satisfies Config;
跟随系统偏好,无需 JS。
// 使用 dark: 前缀
<div className="bg-white dark:bg-gray-900 text-gray-900 dark:text-gray-100">
<h2 className="text-xl font-bold text-gray-800 dark:text-white">
标题
</h2>
<p className="text-gray-600 dark:text-gray-400">
内容自动适配明暗主题。
</p>
</div>
主题切换组件
React 和 Vue 版本的暗色切换按钮
React 版
function ThemeToggle() {
const [dark, setDark] = useState(
localStorage.getItem("theme") === "dark"
);
useEffect(() => {
document.documentElement
.classList.toggle("dark", dark);
localStorage.setItem(
"theme", dark ? "dark" : "light"
);
}, [dark]);
return (
<button onClick={() => setDark(!dark)}>
{dark ? "浅色" : "深色"}
</button>
);
}
Vue 版
<script setup lang="ts">
import { ref, watchEffect } from "vue";
const dark = ref(
localStorage.getItem("theme") === "dark"
);
watchEffect(() => {
document.documentElement
.classList.toggle("dark", dark.value);
localStorage.setItem(
"theme", dark.value ? "dark" : "light"
);
});
</script>
<template>
<button @click="dark = !dark">
{{ dark ? "浅色" : "深色" }}
</button>
</template>
<head> 中加一段同步脚本,在 DOM 渲染前就读取并设置 dark 类名。
组件库暗色适配
Ant Design 和 Element Plus 的暗色模式
Ant Design v5
import { ConfigProvider,
theme } from "antd";
function App() {
return (
<ConfigProvider
theme={{
algorithm:
dark
? theme.darkAlgorithm
: theme.defaultAlgorithm,
token: {
colorPrimary: "#3b82f6",
},
}}
>
{children}
</ConfigProvider>
);
}
Element Plus
$ npm install darkreader
// 引入 Element Plus 暗色 CSS
import "element-plus/theme-chalk/
dark/css-vars.css";
// 切换只需在 html 上加 dark 类
document.documentElement
.classList.toggle("dark");
// Tailwind 和 Element Plus
// 同时生效
关键是统一切换入口。用 Tailwind 的 dark: 处理自定义样式,用组件库自身的暗色方案处理组件。两个系统通过同一个 dark class 联动。
Part 2: 组件库集成
Ant Design(React)和 Element Plus(Vue)的 TypeScript 用法。
组件库提供按钮、表格、表单、弹窗等现成组件,能帮你节省大量开发时间。但直接 npm install 还不够——你还需要了解按需导入、表单验证和主题定制的正确做法。
本部分重点讲类型安全的组件用法和生产级配置,不只是 API 罗列。
Ant Design (React)
组件自带完整 TS 类型,开箱即用
$ npm install antd @ant-design/icons
import { Button, Table, Form, Input, Modal, message } from "antd";
import type { ColumnsType } from "antd/es/table";
// Table 列定义——类型安全
interface User {
key: string;
name: string;
age: number;
email: string;
}
const columns: ColumnsType<User> = [
{ title: "姓名", dataIndex: "name", key: "name" },
{ title: "年龄", dataIndex: "age", key: "age" },
{ title: "邮箱", dataIndex: "email", key: "email" },
];
const data: User[] = [
{ key: "1", name: "张三", age: 25, email: "a@b.c" },
];
// <Table columns={columns} dataSource={data} />
ColumnsType<User> 让列定义和数据类型绑定。如果你写了 dataIndex: "phone",TS 会报错,因为 User 里没有 phone 字段。
Ant Design 表单
Form 和 message 的类型用法
// Form 类型安全
const onFinish = (values: {
username: string;
password: string;
}) => {
console.log(values);
};
// <Form onFinish={onFinish}>
// <Form.Item name="username" rules={[{ required: true }]}>
// <Input />
// </Form.Item>
// <Form.Item name="password" rules={[{ required: true }]}>
// <Input.Password />
// </Form.Item>
// </Form>
// message 的类型
const [messageApi, contextHolder] = message.useMessage();
messageApi.success("操作成功");
messageApi.error("操作失败");
Element Plus (Vue 3)
两种注册方式:全局注册和按需导入
$ npm install element-plus
// 方式一:全局注册(简单但体积大)
import ElementPlus from "element-plus";
import "element-plus/dist/index.css";
app.use(ElementPlus);
# 方式二:按需导入(推荐,体积小)
$ npm install -D unplugin-vue-components unplugin-auto-import
按需导入配置好后,在模板中直接使用 <el-button> 等组件,构建工具会自动导入对应的组件代码和样式。
Element Plus 使用
类型自动推断,无需额外配置
<template>
<el-table :data="tableData">
<el-table-column prop="name" label="姓名" />
<el-table-column prop="age" label="年龄" />
</el-table>
</template>
<script setup lang="ts">
import { ElMessage, ElMessageBox } from "element-plus";
ElMessage.success("操作成功");
// 类型安全的方法
interface FormRules {
username: Array<{
required: boolean;
message: string;
trigger: string;
}>;
}
</script>
按需导入配置
vite.config.ts 完整配置,告别手动 import
// vite.config.ts
import { defineConfig } from "vite";
import vue from "@vitejs/plugin-vue";
import AutoImport from "unplugin-auto-import/vite";
import Components from "unplugin-vue-components/vite";
import { ElementPlusResolver } from "unplugin-vue-components/resolvers";
export default defineConfig({
plugins: [
vue(),
// 自动导入 API:ref, computed 等
AutoImport({
resolvers: [ElementPlusResolver()],
dts: "src/auto-imports.d.ts",
}),
// 自动注册组件:el-button 等
Components({
resolvers: [ElementPlusResolver()],
dts: "src/components.d.ts",
}),
],
});
自动生成的类型文件
auto-imports.d.ts:API 的类型声明
components.d.ts:组件的类型声明
这两个文件不用手动编辑,加入 .gitignore 即可。
常见问题排查
组件没样式?检查 resolver 配置。
TS 报错找不到组件?重启 TS 服务。
样式重复引入?确认只配了一处 resolver。
表单验证深入
不只是 required,还有自定义规则和异步校验
Ant Design 自定义验证
const rules: Rule[] = [
{ required: true,
message: "请输入用户名" },
{ min: 3, max: 20,
message: "3-20个字符" },
{
validator: (_, value) => {
if (/[^a-zA-Z0-9]/.test(value)) {
return Promise.reject(
"只能包含字母和数字"
);
}
return Promise.resolve();
},
},
];
Element Plus 自定义验证
const rules: FormRules = {
username: [
{ required: true,
message: "请输入用户名",
trigger: "blur" },
{ min: 3, max: 20,
message: "3-20个字符",
trigger: "blur" },
{
validator: (_rule, value, callback) => {
if (/[^a-zA-Z0-9]/.test(value)) {
callback(new Error(
"只能包含字母和数字"
));
} else {
callback();
}
},
trigger: "blur",
},
],
};
Zod 表单验证
用 schema 统一前后端验证规则
$ npm install zod
// 1. 定义 schema——前后端共享
import { z } from "zod";
const registerSchema = z.object({
username: z.string()
.min(3, "用户名至少3个字符")
.max(20, "用户名最多20个字符")
.regex(/^[a-zA-Z0-9]+$/, "只能包含字母和数字"),
email: z.string().email("邮箱格式不正确"),
password: z.string()
.min(8, "密码至少8位")
.regex(/[A-Z]/, "需要包含大写字母")
.regex(/[0-9]/, "需要包含数字"),
confirmPassword: z.string(),
}).refine(
(data) => data.password === data.confirmPassword,
{ message: "两次密码不一致", path: ["confirmPassword"] }
);
// 2. 推导 TS 类型
type RegisterForm = z.infer<typeof registerSchema>;
// 3. 在提交时验证
function handleSubmit(values: unknown) {
const result = registerSchema.safeParse(values);
if (!result.success) {
// result.error.issues 包含所有错误信息
const errors = result.error.flatten().fieldErrors;
return;
}
// result.data 是类型安全的
console.log(result.data);
}
Zod 的优势:一个 schema 同时用于前端表单验证和后端 API 参数校验。不用在两个地方各写一遍验证逻辑。
组件库主题定制
让组件库融入你的设计系统
Ant Design v5
import { ConfigProvider,
theme } from "antd";
function App({ children }) {
return (
<ConfigProvider
theme={{
token: {
colorPrimary: "#6366f1",
borderRadius: 8,
fontSize: 14,
fontFamily:
"Inter, system-ui, sans-serif",
},
components: {
Button: {
controlHeight: 40,
},
Table: {
headerBg: "#f8fafc",
},
},
}}
>
{children}
</ConfigProvider>
);
}
Element Plus
/* 方式一:CSS 变量覆盖 */
:root {
--el-color-primary: #6366f1;
--el-border-radius-base: 8px;
--el-font-size-base: 14px;
}
/* 方式二:SCSS 变量(需配置) */
// vite.config.ts 加 scss 预处理
css: {
preprocessorOptions: {
scss: {
additionalData: `
$--color-primary: #6366f1;
$--border-radius-base: 8px;
`,
},
},
}
核心思路:先定好品牌色、圆角、字号三个 token,然后在 ConfigProvider 或 CSS 变量中统一设置。组件会自动继承这些 token。
Part 3: 图标库
在 TS 项目中类型安全地使用图标。
图标看似简单,但在 TypeScript 项目中有很多细节:如何类型安全地传递图标组件?如何动态选择图标?如何封装自定义 SVG?本部分给出实用的模式。
React 图标
lucide-react 和 @ant-design/icons
import { Home, User, Settings } from "lucide-react";
// 类型安全的导航配置
interface NavItem {
icon: React.ComponentType<{ size?: number }>;
label: string;
path: string;
}
const nav: NavItem[] = [
{ icon: Home, label: "首页", path: "/" },
{ icon: User, label: "用户", path: "/users" },
{ icon: Settings, label: "设置", path: "/settings" },
];
// 图标类型映射——动态选择图标
type IconName = "home" | "user" | "settings";
const iconMap: Record<IconName, React.ComponentType> = {
home: Home,
user: User,
settings: Settings,
};
function DynamicIcon({ name }: { name: IconName }) {
const Icon = iconMap[name];
return <Icon />;
}
Vue 图标
@element-plus/icons-vue
<script setup lang="ts">
import { HomeFilled, User, Setting } from "@element-plus/icons-vue";
import type { Component } from "vue";
interface NavItem {
icon: Component;
label: string;
path: string;
}
const nav: NavItem[] = [
{ icon: HomeFilled, label: "首页", path: "/" },
{ icon: User, label: "用户", path: "/users" },
{ icon: Setting, label: "设置", path: "/settings" },
];
</script>
自定义 SVG 图标
封装自己的图标组件
// React 自定义图标组件
interface IconProps {
size?: number;
color?: string;
className?: string;
}
function CustomIcon({
size = 24,
color = "currentColor",
className,
}: IconProps) {
return (
<svg width={size} height={size} fill={color} className={className}>
{/* SVG path */}
</svg>
);
}
自定义图标的 Props 用 interface 定义好类型,其他组件引用时就能获得完整的类型提示。这就是 TypeScript 在 UI 开发中的价值:每一处输入都有类型保护。
Part 4: Headless UI
当组件库满足不了你的设计需求
Ant Design 和 Element Plus 功能全面,但样式定制有时不够灵活。Headless UI 提供了另一种思路:只提供行为逻辑,样式完全由你控制。本部分简要介绍这个方向。
什么是 Headless
逻辑和样式分离的组件设计模式
Headless 组件只提供交互逻辑(打开/关闭、键盘导航、ARIA 属性),不提供任何默认样式。你用 Tailwind 或自定义 CSS 来决定外观。
Radix UI
React 生态最流行的 headless 库。提供 Dialog、Popover、Tooltip 等 20+ 组件。Shadcn/ui 就是基于 Radix UI 构建的。
Headless UI
Tailwind 官方团队维护。同时支持 React 和 Vue。提供 Listbox、Combobox、Switch 等表单增强组件。
Shadcn/ui
不是npm 包,而是一套可复制的组件代码。基于 Radix UI + Tailwind。你拥有完整源码,可以随意修改。
何时选择 Headless
Styled 库 vs Headless 库的取舍
选 Styled 组件库
Ant Design、Element Plus 这类:
- 后台管理系统,追求开发速度
- 设计规范不严格,默认样式能接受
- 需要表格、表单等复杂组件
- 团队成员 UI 经验较少
选 Headless 方案
Radix、Shadcn/ui 这类:
- 面向用户的产品,有严格的设计稿
- 默认样式无法满足品牌要求
- 需要极致的包体积优化
- 团队有专职设计师
两者并不冲突。很多项目用 Ant Design 做后台页面,同时用 Shadcn/ui 做面向用户的前台。按场景选择最合适的工具。
Shadcn 组件结构
看看一个 Shadcn Button 长什么样
# 用 CLI 添加组件(不是 npm install)
$ npx shadcn@latest add button
// src/components/ui/button.tsx
// 这是复制到你项目里的源码,你可以随意改
import * as React from "react";
import { Slot } from "@radix-ui/react-slot";
import { cva, type VariantProps } from "class-variance-authority";
import { cn } from "@/lib/utils";
const buttonVariants = cva(
"inline-flex items-center justify-center rounded-md text-sm font-medium",
{
variants: {
variant: {
default: "bg-primary text-white hover:bg-primary/90",
destructive: "bg-red-500 text-white hover:bg-red-600",
outline: "border border-gray-300 hover:bg-gray-100",
ghost: "hover:bg-gray-100",
},
size: {
default: "h-10 px-4 py-2",
sm: "h-9 rounded-md px-3",
lg: "h-11 rounded-md px-8",
},
},
defaultVariants: {
variant: "default",
size: "default",
},
}
);
interface ButtonProps
extends React.ButtonHTMLAttributes<HTMLButtonElement>,
VariantProps<typeof buttonVariants> {
asChild?: boolean;
}
const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
({ className, variant, size, asChild = false, ...props }, ref) => {
const Comp = asChild ? Slot : "button";
return (
<Comp
className={cn(buttonVariants({ variant, size, className }))}
ref={ref}
{...props}
/>
);
}
);
关键点:cva 管理变体样式,cn 合并类名,VariantProps 自动推导类型。你可以直接修改这段代码来适配设计稿。
常见排错
问题一:Tailwind 类名不生效
原因:tailwind.config.ts 的 content 路径没配对,Tailwind 扫描不到你的组件文件。
修复:确认 content 数组包含了所有使用 Tailwind 类名的文件路径。
问题二:Ant Design 组件样式错乱
原因:CSS 导入顺序不对,或者和全局样式冲突。
修复:确保 import "antd/dist/reset.css" 在全局样式之前导入。
问题三:Element Plus 按需导入不工作
原因:unplugin-vue-components 的 resolver 没有配置。
修复:在 vite.config.ts 中添加 Element Plus resolver 配置。
问题四:暗黑模式切换后组件库没反应
原因:只加了 Tailwind 的 dark class,但没有配置组件库的暗色方案。
修复:Ant Design 需要切换 algorithm,Element Plus 需要引入 dark/css-vars.css。
问题五:Shadcn 组件 TS 报类型错误
原因:缺少 class-variance-authority 或 @radix-ui/* 的类型依赖。
修复:确保 npx shadcn@latest add button 时所有依赖都已自动安装。手动安装可以用 npm install class-variance-authority clsx tailwind-merge。
试一试
练习一
任务:在 React + TS 项目中接入 Tailwind CSS,创建一个 Card 组件,支持 title、description 和 variant("outlined" | "elevated")三个 Props,用 Tailwind 类名实现不同样式。
// 提示:用联合类型约束 variant
// 你的代码:
参考答案
interface CardProps {
title: string;
description: string;
variant?: "outlined" | "elevated";
}
function Card({ title, description, variant = "outlined" }: CardProps) {
const base = "p-4 rounded-lg";
const variants = {
outlined: "border border-gray-300",
elevated: "shadow-lg",
};
return (
<div className={`${base} ${variants[variant]}`}>
<h3 className="text-lg font-bold">{title}</h3>
<p className="text-gray-600">{description}</p>
</div>
);
}
练习二
任务:在 Vue 3 + TS 项目中接入 Element Plus,创建一个用户列表页面,使用 el-table 展示数据,用 ElMessage 在删除时弹出提示。
<!-- 提示:interface User { id: string; name: string; age: number }
<!-- 你的代码: -->
参考答案
<template>
<el-table :data="users">
<el-table-column prop="name" label="姓名" />
<el-table-column prop="age" label="年龄" />
<el-table-column label="操作">
<template #default="{ row }">
<el-button @click="handleDelete(row.id)">
删除
</el-button>
</template>
</el-table-column>
</el-table>
</template>
<script setup lang="ts">
import { ref } from "vue";
import { ElMessage } from "element-plus";
interface User {
id: string; name: string; age: number;
}
const users = ref<User[]>([
{ id: "1", name: "张三", age: 25 },
{ id: "2", name: "李四", age: 30 },
]);
function handleDelete(id: string) {
users.value = users.value.filter(u => u.id !== id);
ElMessage.success("删除成功");
}
</script>
📝 要点总结
本章核心知识点
Tailwind CSS
原子化 CSS、响应式断点、暗黑模式、主题扩展
组件库
Ant Design(React)、Element Plus(Vue 3)、按需导入
表单验证
Zod schema 定义、类型推导、与表单库集成
Headless UI
无样式组件、Shadcn 架构、自定义扩展
主题与图标
CSS 变量主题定制、图标库集成、SVG 图标组件
下一章
UI 框架集成搞定了。下一章我们回到后端,学习 Node.js 与 TypeScript 服务端基础——把 TS 的类型安全带到服务器端。