
AI辅助的UI开发正在改变传统的设计实现流程,通过shadcn和TailwindCSS的强大组件生态,结合AI的智能生成能力,可以极大提升界面开发效率。
在现代前端开发中,快速实现高质量的用户界面已成为开发效率的关键因素。shadcn/ui组件库配合TailwindCSS提供了一套优雅的UI组件解决方案,而AI工具的引入则为这一流程带来了革命性的变化。通过合理利用AI的智能生成能力,开发者可以更快地实现设计想法,保持设计系统的一致性,并减少重复性的编码工作。
shadcn/ui是一套基于Radix UI和TailwindCSS的可访问、可定制的组件库。
# 初始化shadcn项目
npx shadcn-ui@latest init
# 添加组件
npx shadcn-ui@latest add button card input label textarea// 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 whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50",
{
variants: {
variant: {
default: "bg-primary text-primary-foreground hover:bg-primary/90",
destructive: "bg-destructive text-destructive-foreground hover:bg-destructive/90",
outline: "border border-input bg-background hover:bg-accent hover:text-accent-foreground",
secondary: "bg-secondary text-secondary-foreground hover:bg-secondary/80",
ghost: "hover:bg-accent hover:text-accent-foreground",
link: "text-primary underline-offset-4 hover:underline",
},
size: {
default: "h-10 px-4 py-2",
sm: "h-9 rounded-md px-3",
lg: "h-11 rounded-md px-8",
icon: "h-10 w-10",
},
},
defaultVariants: {
variant: "default",
size: "default",
},
}
)
export 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}
/>
)
}
)
Button.displayName = "Button"
export { Button, buttonVariants }// tailwind.config.js
/** @type {import('tailwindcss').Config} */
module.exports = {
darkMode: ["class"],
content: [
'./pages/**/*.{ts,tsx}',
'./components/**/*.{ts,tsx}',
'./app/**/*.{ts,tsx}',
'./src/**/*.{ts,tsx}',
],
theme: {
container: {
center: true,
padding: "2rem",
screens: {
"2xl": "1400px",
},
},
extend: {
colors: {
border: "hsl(var(--border))",
input: "hsl(var(--input))",
ring: "hsl(var(--ring))",
background: "hsl(var(--background))",
foreground: "hsl(var(--foreground))",
primary: {
DEFAULT: "hsl(var(--primary))",
foreground: "hsl(var(--primary-foreground))",
},
secondary: {
DEFAULT: "hsl(var(--secondary))",
foreground: "hsl(var(--secondary-foreground))",
},
destructive: {
DEFAULT: "hsl(var(--destructive))",
foreground: "hsl(var(--destructive-foreground))",
},
muted: {
DEFAULT: "hsl(var(--muted))",
foreground: "hsl(var(--muted-foreground))",
},
accent: {
DEFAULT: "hsl(var(--accent))",
foreground: "hsl(var(--accent-foreground))",
},
popover: {
DEFAULT: "hsl(var(--popover))",
foreground: "hsl(var(--popover-foreground))",
},
card: {
DEFAULT: "hsl(var(--card))",
foreground: "hsl(var(--card-foreground))",
},
},
borderRadius: {
lg: "var(--radius)",
md: "calc(var(--radius) - 2px)",
sm: "calc(var(--radius) - 4px)",
},
keyframes: {
"accordion-down": {
from: { height: 0 },
to: { height: "var(--radix-accordion-content-height)" },
},
"accordion-up": {
from: { height: "var(--radix-accordion-content-height)" },
to: { height: 0 },
},
},
animation: {
"accordion-down": "accordion-down 0.2s ease-out",
"accordion-up": "accordion-up 0.2s ease-out",
},
},
},
plugins: [require("tailwindcss-animate")],
}// 使用AI生成复杂表单组件
// Prompt: "Create a form component with validation, loading states, and success feedback"
import { useState } from 'react';
import { Button } from '@/components/ui/button';
import { Input } from '@/components/ui/input';
import { Label } from '@/components/ui/label';
import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from '@/components/ui/card';
interface FormState {
name: string;
email: string;
message: string;
}
interface FormErrors {
name?: string;
email?: string;
message?: string;
}
const ContactForm = () => {
const [formData, setFormData] = useState<FormState>({ name: '', email: '', message: '' });
const [errors, setErrors] = useState<FormErrors>({});
const [isSubmitting, setIsSubmitting] = useState(false);
const [isSubmitted, setIsSubmitted] = useState(false);
const validate = (): boolean => {
const newErrors: FormErrors = {};
if (!formData.name.trim()) {
newErrors.name = 'Name is required';
}
if (!formData.email.trim()) {
newErrors.email = 'Email is required';
} else if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(formData.email)) {
newErrors.email = 'Invalid email format';
}
if (!formData.message.trim()) {
newErrors.message = 'Message is required';
}
setErrors(newErrors);
return Object.keys(newErrors).length === 0;
};
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
if (!validate()) return;
setIsSubmitting(true);
try {
// Simulate API call
await new Promise(resolve => setTimeout(resolve, 1500));
setIsSubmitted(true);
setFormData({ name: '', email: '', message: '' });
} catch (error) {
console.error('Form submission error:', error);
} finally {
setIsSubmitting(false);
}
};
if (isSubmitted) {
return (
<Card className="w-full max-w-md mx-auto mt-8">
<CardHeader>
<CardTitle>Thank You!</CardTitle>
<CardDescription>Your message has been sent successfully.</CardDescription>
</CardHeader>
<CardFooter>
<Button
onClick={() => setIsSubmitted(false)}
className="w-full"
>
Send Another Message
</Button>
</CardFooter>
</Card>
);
}
return (
<Card className="w-full max-w-md mx-auto mt-8">
<CardHeader>
<CardTitle>Contact Us</CardTitle>
<CardDescription>Fill out the form to get in touch.</CardDescription>
</CardHeader>
<CardContent>
<form onSubmit={handleSubmit} className="space-y-4">
<div className="space-y-2">
<Label htmlFor="name">Name</Label>
<Input
id="name"
value={formData.name}
onChange={(e) => setFormData({...formData, name: e.target.value})}
className={errors.name ? 'border-red-500' : ''}
placeholder="Enter your name"
/>
{errors.name && <p className="text-sm text-red-500">{errors.name}</p>}
</div>
<div className="space-y-2">
<Label htmlFor="email">Email</Label>
<Input
id="email"
type="email"
value={formData.email}
onChange={(e) => setFormData({...formData, email: e.target.value})}
className={errors.email ? 'border-red-500' : ''}
placeholder="Enter your email"
/>
{errors.email && <p className="text-sm text-red-500">{errors.email}</p>}
</div>
<div className="space-y-2">
<Label htmlFor="message">Message</Label>
<textarea
id="message"
value={formData.message}
onChange={(e) => setFormData({...formData, message: e.target.value})}
className={`w-full resize-none rounded-md border px-3 py-2 ${errors.message ? 'border-red-500' : 'border-input'}`}
placeholder="Enter your message"
rows={4}
/>
{errors.message && <p className="text-sm text-red-500">{errors.message}</p>}
</div>
</form>
</CardContent>
<CardFooter>
<Button
type="submit"
onClick={handleSubmit}
disabled={isSubmitting}
className="w-full"
>
{isSubmitting ? (
<span className="flex items-center">
<svg className="animate-spin -ml-1 mr-3 h-5 w-5 text-current" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">
<circle className="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="4"></circle>
<path className="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
</svg>
Sending...
</span>
) : 'Send Message'}
</Button>
</CardFooter>
</Card>
);
};
export default ContactForm;// 使用AI生成仪表板布局
// Prompt: "Create a responsive dashboard layout with sidebar, header, and content area using shadcn components"
import { useState } from 'react';
import { Button } from '@/components/ui/button';
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card';
import { Sheet, SheetContent, SheetTrigger } from '@/components/ui/sheet';
import { Skeleton } from '@/components/ui/skeleton';
import { Menu, Package, Users, LineChart, Settings, CreditCard, Truck } from 'lucide-react';
const DashboardLayout = () => {
const [sidebarOpen, setSidebarOpen] = useState(false);
const [activeTab, setActiveTab] = useState('dashboard');
const sidebarItems = [
{ id: 'dashboard', label: 'Dashboard', icon: LineChart },
{ id: 'orders', label: 'Orders', icon: Package },
{ id: 'products', label: 'Products', icon: CreditCard },
{ id: 'customers', label: 'Customers', icon: Users },
{ id: 'analytics', label: 'Analytics', icon: LineChart },
{ id: 'delivery', label: 'Delivery', icon: Truck },
{ id: 'settings', label: 'Settings', icon: Settings },
];
const renderContent = () => {
switch(activeTab) {
case 'dashboard':
return <DashboardContent />;
case 'orders':
return <OrdersContent />;
case 'products':
return <ProductsContent />;
default:
return <DashboardContent />;
}
};
return (
<div className="flex h-screen bg-muted/40">
{/* Mobile sidebar */}
<Sheet open={sidebarOpen} onOpenChange={setSidebarOpen}>
<SheetTrigger asChild>
<Button
variant="outline"
size="icon"
className="absolute left-4 top-4 z-50 flex h-8 w-8 items-center justify-center rounded-lg bg-background md:hidden"
>
<Menu className="h-4 w-4" />
<span className="sr-only">Toggle navigation menu</span>
</Button>
</SheetTrigger>
<SheetContent side="left" className="inset-y-0 z-50 w-72 bg-background p-0">
<nav className="grid gap-2 py-4 text-lg font-medium">
{sidebarItems.map((item) => {
const Icon = item.icon;
return (
<button
key={item.id}
onClick={() => {
setActiveTab(item.id);
setSidebarOpen(false);
}}
className={`mx-[-0.65rem] flex items-center gap-4 rounded-xl px-4 py-3 ${
activeTab === item.id
? 'bg-muted text-primary'
: 'text-muted-foreground hover:text-foreground'
}`}
>
<Icon className="h-5 w-5" />
{item.label}
</button>
);
})}
</nav>
</SheetContent>
</Sheet>
{/* Desktop sidebar */}
<aside className="hidden md:flex md:w-72 md:flex-col">
<div className="flex h-16 items-center border-b px-6">
<h2 className="text-lg font-semibold">Acme Inc</h2>
</div>
<nav className="grid flex-1 items-start gap-2 p-4 text-lg font-medium">
{sidebarItems.map((item) => {
const Icon = item.icon;
return (
<button
key={item.id}
onClick={() => setActiveTab(item.id)}
className={`mx-[-0.65rem] flex items-center gap-4 rounded-xl px-4 py-3 ${
activeTab === item.id
? 'bg-muted text-primary'
: 'text-muted-foreground hover:text-foreground'
}`}
>
<Icon className="h-5 w-5" />
{item.label}
</button>
);
})}
</nav>
</aside>
<div className="flex flex-col flex-1 overflow-hidden">
<header className="sticky top-0 z-40 flex h-16 shrink-0 items-center gap-4 border-b bg-background px-6">
<div className="ml-auto flex items-center gap-4">
<Button variant="outline" size="icon" className="h-8 w-8 rounded-lg">
<Settings className="h-4 w-4" />
</Button>
<div className="h-8 w-8 rounded-full bg-muted flex items-center justify-center">
<span className="text-sm font-medium">JD</span>
</div>
</div>
</header>
<main className="flex-1 overflow-y-auto p-6">
{renderContent()}
</main>
</div>
</div>
);
};
// 仪表板内容组件
const DashboardContent = () => {
return (
<div className="space-y-4">
<div className="grid gap-4 md:grid-cols-2 lg:grid-cols-4">
<Card>
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
<CardTitle className="text-sm font-medium">Total Revenue</CardTitle>
<svg xmlns="http://www.w3.org/2000/svg" className="h-4 w-4 text-muted-foreground" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M12 8c-1.657 0-3 .895-3 2s1.343 2 3 2 3 .895 3 2-1.343 2-3 2m0-8c1.11 0 2.08.402 2.599 1M12 8V7m0 1v8m0 0v1m0-1c-1.11 0-2.08-.402-2.599-1M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
</svg>
</CardHeader>
<CardContent>
<div className="text-2xl font-bold">$45,231.89</div>
<p className="text-xs text-muted-foreground">+20.1% from last month</p>
</CardContent>
</Card>
<Card>
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
<CardTitle className="text-sm font-medium">Subscriptions</CardTitle>
<svg xmlns="http://www.w3.org/2000/svg" className="h-4 w-4 text-muted-foreground" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M16 7a4 4 0 11-8 0 4 4 0 018 0zM12 14a7 7 0 00-7 7h14a7 7 0 00-7-7z" />
</svg>
</CardHeader>
<CardContent>
<div className="text-2xl font-bold">+2350</div>
<p className="text-xs text-muted-foreground">+180.1% from last month</p>
</CardContent>
</Card>
<Card>
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
<CardTitle className="text-sm font-medium">Sales</CardTitle>
<svg xmlns="http://www.w3.org/2000/svg" className="h-4 w-4 text-muted-foreground" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M3 6l3 1m0 0l-3 9a5.002 5.002 0 006.001 0M6 7l3 9M6 7l6-2m6 2l3-1m-3 1l-3 9a5.002 5.002 0 006.001 0M18 7l3 9m-3-9l-6-2m0-2v2m0 16V5m0 16H9m3 0h3" />
</svg>
</CardHeader>
<CardContent>
<div className="text-2xl font-bold">+12,234</div>
<p className="text-xs text-muted-foreground">+19% from last month</p>
</CardContent>
</Card>
<Card>
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
<CardTitle className="text-sm font-medium">Active Now</CardTitle>
<svg xmlns="http://www.w3.org/2000/svg" className="h-4 w-4 text-muted-foreground" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M9 12l2 2 4-4m5.618-4.016A11.955 11.955 0 0112 2.944a11.955 11.955 0 01-8.618 3.04A12.02 12.02 0 003 9c0 5.591 3.824 10.29 9 11.622 5.176-1.332 9-6.03 9-11.622 0-1.042-.133-2.052-.382-3.016z" />
</svg>
</CardHeader>
<CardContent>
<div className="text-2xl font-bold">+573</div>
<p className="text-xs text-muted-foreground">+201 since last hour</p>
</CardContent>
</Card>
</div>
<div className="grid gap-4 md:grid-cols-2 lg:grid-cols-7">
<Card className="col-span-4">
<CardHeader>
<CardTitle>Overview</CardTitle>
</CardHeader>
<CardContent className="pl-2">
<div className="h-[200px] flex items-center justify-center">
<Skeleton className="h-40 w-full" />
</div>
</CardContent>
</Card>
<Card className="col-span-3">
<CardHeader>
<CardTitle>Recent Sales</CardTitle>
<CardDescription>You made 265 sales this month.</CardDescription>
</CardHeader>
<CardContent>
<div className="space-y-8">
{[1, 2, 3, 4].map((item) => (
<div key={item} className="flex items-center">
<div className="ml-4 space-y-1">
<p className="text-sm font-medium leading-none">Liam Johnson</p>
<p className="text-sm text-muted-foreground">liam@example.com</p>
</div>
<div className="ml-auto font-medium">+$265.00</div>
</div>
))}
</div>
</CardContent>
</Card>
</div>
</div>
);
};
const OrdersContent = () => {
return (
<Card>
<CardHeader>
<CardTitle>Orders</CardTitle>
<CardDescription>Manage your orders here.</CardDescription>
</CardHeader>
<CardContent>
<div className="h-96 flex items-center justify-center">
<p className="text-muted-foreground">Orders content will be displayed here</p>
</div>
</CardContent>
</Card>
);
};
const ProductsContent = () => {
return (
<Card>
<CardHeader>
<CardTitle>Products</CardTitle>
<CardDescription>Manage your products here.</CardDescription>
</CardHeader>
<CardContent>
<div className="h-96 flex items-center justify-center">
<p className="text-muted-foreground">Products content will be displayed here</p>
</div>
</CardContent>
</Card>
);
};
export default DashboardLayout;# AI UI生成提示词模板
## 基础组件生成Create a [component type] component with [styling framework] that [functionality description]. The component should be accessible and follow best practices.
## 复杂布局生成Create a responsive [layout type] layout using [frameworks] with the following requirements:
The design should follow modern UI/UX principles and be mobile-responsive.
## 表单验证生成Create a form component with [number] fields that includes:
Use [UI library] components and [CSS framework] for styling.
```
### 实用AI提示词示例
```typescript
// 提示词:生成带有数据表格的管理界面
/*
Create a data management interface with the following components:
1. Searchable and filterable data table
2. Row selection capabilities
3. Bulk actions toolbar
4. Pagination controls
5. Loading and empty states
Use shadcn/ui components including:
- Table with advanced features
- Dialog for editing records
- Dropdown for bulk actions
- Combobox for filtering
The design should be fully responsive and follow accessibility standards.
Include sample data and mock API integration.
*/
// 生成的数据表格组件
import { useState, useEffect } from 'react';
import { Button } from '@/components/ui/button';
import { Input } from '@/components/ui/input';
import {
Table,
TableBody,
TableCell,
TableHead,
TableHeader,
TableRow
} from '@/components/ui/table';
import { Checkbox } from '@/components/ui/checkbox';
import { Dialog, DialogContent, DialogHeader, DialogTitle } from '@/components/ui/dialog';
import { Badge } from '@/components/ui/badge';
interface User {
id: string;
name: string;
email: string;
role: string;
status: 'active' | 'inactive' | 'pending';
joinedDate: string;
}
const UserManager = () => {
const [users, setUsers] = useState<User[]>([]);
const [filteredUsers, setFilteredUsers] = useState<User[]>([]);
const [searchTerm, setSearchTerm] = useState('');
const [selectedRows, setSelectedRows] = useState<Set<string>>(new Set());
const [isDialogOpen, setIsDialogOpen] = useState(false);
const [editingUser, setEditingUser] = useState<User | null>(null);
const [loading, setLoading] = useState(true);
// 模拟数据加载
useEffect(() => {
const mockUsers: User[] = [
{ id: '1', name: 'John Doe', email: 'john@example.com', role: 'Admin', status: 'active', joinedDate: '2023-01-15' },
{ id: '2', name: 'Jane Smith', email: 'jane@example.com', role: 'User', status: 'active', joinedDate: '2023-02-20' },
{ id: '3', name: 'Bob Johnson', email: 'bob@example.com', role: 'Editor', status: 'inactive', joinedDate: '2023-03-10' },
{ id: '4', name: 'Alice Williams', email: 'alice@example.com', role: 'User', status: 'pending', joinedDate: '2023-04-05' },
{ id: '5', name: 'Charlie Brown', email: 'charlie@example.com', role: 'Admin', status: 'active', joinedDate: '2023-05-12' },
];
setTimeout(() => {
setUsers(mockUsers);
setFilteredUsers(mockUsers);
setLoading(false);
}, 500);
}, []);
// 搜索过滤
useEffect(() => {
if (!searchTerm) {
setFilteredUsers(users);
} else {
const filtered = users.filter(user =>
user.name.toLowerCase().includes(searchTerm.toLowerCase()) ||
user.email.toLowerCase().includes(searchTerm.toLowerCase()) ||
user.role.toLowerCase().includes(searchTerm.toLowerCase())
);
setFilteredUsers(filtered);
}
}, [searchTerm, users]);
const toggleRowSelection = (id: string) => {
const newSelected = new Set(selectedRows);
if (newSelected.has(id)) {
newSelected.delete(id);
} else {
newSelected.add(id);
}
setSelectedRows(newSelected);
};
const toggleSelectAll = () => {
if (selectedRows.size === filteredUsers.length) {
setSelectedRows(new Set());
} else {
const allIds = new Set(filteredUsers.map(user => user.id));
setSelectedRows(allIds);
}
};
const handleBulkDelete = () => {
if (window.confirm(`Are you sure you want to delete ${selectedRows.size} users?`)) {
setUsers(users.filter(user => !selectedRows.has(user.id)));
setSelectedRows(new Set());
}
};
const handleEditUser = (user: User) => {
setEditingUser(user);
setIsDialogOpen(true);
};
const handleDialogSave = (updatedUser: User) => {
setUsers(users.map(user => user.id === updatedUser.id ? updatedUser : user));
setIsDialogOpen(false);
setEditingUser(null);
};
const getStatusBadgeVariant = (status: User['status']) => {
switch(status) {
case 'active': return 'bg-green-500';
case 'inactive': return 'bg-gray-500';
case 'pending': return 'bg-yellow-500';
default: return 'bg-gray-500';
}
};
if (loading) {
return (
<div className="p-6">
<div className="animate-pulse space-y-4">
<div className="h-10 bg-muted rounded w-1/4"></div>
<div className="h-96 bg-muted rounded"></div>
</div>
</div>
);
}
return (
<div className="p-6 space-y-6">
<div className="flex flex-col sm:flex-row sm:items-center sm:justify-between gap-4">
<h1 className="text-2xl font-bold">User Management</h1>
<div className="flex gap-2">
<Button
onClick={() => console.log('Add new user')}
disabled={selectedRows.size > 0}
>
Add User
</Button>
{selectedRows.size > 0 && (
<Button
variant="destructive"
onClick={handleBulkDelete}
>
Delete Selected ({selectedRows.size})
</Button>
)}
</div>
</div>
<div className="flex flex-col sm:flex-row gap-4">
<div className="relative flex-1">
<Input
placeholder="Search users..."
value={searchTerm}
onChange={(e) => setSearchTerm(e.target.value)}
className="pl-10"
/>
<svg
className="absolute left-3 top-1/2 transform -translate-y-1/2 h-4 w-4 text-muted-foreground"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z"></path>
</svg>
</div>
</div>
<div className="rounded-md border">
<Table>
<TableHeader>
<TableRow>
<TableHead className="w-12">
<Checkbox
checked={selectedRows.size === filteredUsers.length && filteredUsers.length > 0}
onCheckedChange={toggleSelectAll}
/>
</TableHead>
<TableHead>Name</TableHead>
<TableHead>Email</TableHead>
<TableHead>Role</TableHead>
<TableHead>Status</TableHead>
<TableHead>Joined</TableHead>
<TableHead className="text-right">Actions</TableHead>
</TableRow>
</TableHeader>
<TableBody>
{filteredUsers.length === 0 ? (
<TableRow>
<TableCell colSpan={7} className="h-24 text-center">
No users found
</TableCell>
</TableRow>
) : (
filteredUsers.map((user) => (
<TableRow key={user.id}>
<TableCell>
<Checkbox
checked={selectedRows.has(user.id)}
onCheckedChange={() => toggleRowSelection(user.id)}
/>
</TableCell>
<TableCell className="font-medium">{user.name}</TableCell>
<TableCell>{user.email}</TableCell>
<TableCell>{user.role}</TableCell>
<TableCell>
<Badge className={getStatusBadgeVariant(user.status)}>
{user.status}
</Badge>
</TableCell>
<TableCell>{user.joinedDate}</TableCell>
<TableCell className="text-right">
<Button
variant="ghost"
size="sm"
onClick={() => handleEditUser(user)}
>
Edit
</Button>
</TableCell>
</TableRow>
))
)}
</TableBody>
</Table>
</div>
<div className="flex items-center justify-between">
<p className="text-sm text-muted-foreground">
Showing {filteredUsers.length} of {users.length} users
</p>
<div className="flex gap-2">
<Button variant="outline" size="sm">
Previous
</Button>
<Button size="sm">
Next
</Button>
</div>
</div>
{/* 编辑对话框 */}
<Dialog open={isDialogOpen} onOpenChange={setIsDialogOpen}>
<DialogContent>
<DialogHeader>
<DialogTitle>Edit User</DialogTitle>
</DialogHeader>
{editingUser && (
<EditUserForm
user={editingUser}
onSave={handleDialogSave}
onCancel={() => setIsDialogOpen(false)}
/>
)}
</DialogContent>
</Dialog>
</div>
);
};
// 编辑用户表单组件
const EditUserForm = ({ user, onSave, onCancel }: {
user: User;
onSave: (updatedUser: User) => void;
onCancel: () => void
}) => {
const [formData, setFormData] = useState<User>(user);
const handleSubmit = (e: React.FormEvent) => {
e.preventDefault();
onSave(formData);
};
return (
<form onSubmit={handleSubmit} className="space-y-4">
<div>
<label className="block text-sm font-medium mb-1">Name</label>
<Input
value={formData.name}
onChange={(e) => setFormData({...formData, name: e.target.value})}
/>
</div>
<div>
<label className="block text-sm font-medium mb-1">Email</label>
<Input
type="email"
value={formData.email}
onChange={(e) => setFormData({...formData, email: e.target.value})}
/>
</div>
<div>
<label className="block text-sm font-medium mb-1">Role</label>
<select
value={formData.role}
onChange={(e) => setFormData({...formData, role: e.target.value})}
className="w-full rounded-md border border-input bg-background px-3 py-2"
>
<option value="Admin">Admin</option>
<option value="Editor">Editor</option>
<option value="User">User</option>
</select>
</div>
<div className="flex justify-end gap-2 pt-4">
<Button type="button" variant="outline" onClick={onCancel}>
Cancel
</Button>
<Button type="submit">Save Changes</Button>
</div>
</form>
);
};
export default UserManager;// 优化的表格组件 - 使用虚拟滚动
import { useState, useMemo, useCallback } from 'react';
import { FixedSizeList as List } from 'react-window';
const VirtualizedTable = ({ data, columns }) => {
const [sortConfig, setSortConfig] = useState<{ key: string; direction: 'asc' | 'desc' } | null>(null);
const [filterText, setFilterText] = useState('');
// 数据过滤和排序
const processedData = useMemo(() => {
let filtered = data;
// 过滤
if (filterText) {
filtered = data.filter(item =>
Object.values(item).some(val =>
String(val).toLowerCase().includes(filterText.toLowerCase())
)
);
}
// 排序
if (sortConfig !== null) {
filtered = [...filtered].sort((a, b) => {
if (a[sortConfig.key] < b[sortConfig.key]) {
return sortConfig.direction === 'asc' ? -1 : 1;
}
if (a[sortConfig.key] > b[sortConfig.key]) {
return sortConfig.direction === 'asc' ? 1 : -1;
}
return 0;
});
}
return filtered;
}, [data, filterText, sortConfig]);
const handleSort = useCallback((key: string) => {
let direction: 'asc' | 'desc' = 'asc';
if (sortConfig && sortConfig.key === key && sortConfig.direction === 'asc') {
direction = 'desc';
}
setSortConfig({ key, direction });
}, [sortConfig]);
const Row = ({ index, style }) => {
const item = processedData[index];
return (
<div style={style} className="flex border-b hover:bg-muted/50">
{columns.map((column, colIndex) => (
<div key={colIndex} className="flex-1 p-2 truncate">
{item[column.key]}
</div>
))}
</div>
);
};
return (
<div className="space-y-4">
<div className="flex gap-2">
<input
type="text"
placeholder="Filter..."
value={filterText}
onChange={(e) => setFilterText(e.target.value)}
className="flex-1 rounded-md border border-input px-3 py-2"
/>
</div>
<div className="rounded-md border">
<div className="flex border-b font-medium">
{columns.map((column, index) => (
<div
key={index}
className="flex-1 p-2 cursor-pointer hover:bg-muted"
onClick={() => handleSort(column.key)}
>
{column.title}
{sortConfig?.key === column.key && (
<span>{sortConfig.direction === 'asc' ? ' ↑' : ' ↓'}</span>
)}
</div>
))}
</div>
<List
height={400}
itemCount={processedData.length}
itemSize={40}
width="100%"
>
{Row}
</List>
</div>
</div>
);
};AI辅助的界面开发能够显著提升开发效率,但需要结合良好的组件设计和性能优化策略,确保生成的代码既高效又可维护。
AI配合shadcn和TailwindCSS的界面生成方式代表了现代前端开发的趋势。通过合理利用AI工具,我们可以:
然而,我们也需要注意AI生成代码的审查和优化,确保生成的组件符合项目需求并具备良好的性能特征。通过合理运用AI工具和现代UI框架,我们可以构建出高效、美观且可维护的用户界面。