AI编程工具实战:Cursor + React + TypeScript 开发电商后台管理系统
表单组件重复性高(CRUD表单、筛选面板、分页器)状态管理逻辑复杂(筛选、排序、批量操作)样式开发耗时(响应式布局、深浅色切换)代码规范难以统一(多人协作时尤其明显)用AI辅助开发,可以把70%的重复代码交给AI处理,开发者专注于业务逻辑和架构设计。搭建了完整的技术栈实现了核心功能:商品CRUD、筛选排序、批量操作、深色模式展示了AI协作流程:需求分析 → 类型定义 → 组件生成 → 状态管理补充
2026年了,不会还有人觉得"会用Copilot写注释"就等于掌握AI编程吧?
说实话,我在找实习的过程中发现,面试官现在最喜欢问的问题已经从"你用过什么框架"变成了"你用过什么AI编程工具?怎么用的?用的怎么样?"
今天这篇博客,不整虚的,直接带你用 Cursor + React + TypeScript + Tailwind CSS 从零搭建一个完整的电商后台管理系统。手把手的那种,有截图、有代码、有踩坑记录。
读完这篇,你收获的不只是一个项目,更是一套AI时代的开发方法论。
一、项目介绍:为什么选这个场景?
1.1 解决什么问题
传统后台管理系统开发有几个痛点:
- 表单组件重复性高(CRUD表单、筛选面板、分页器)
- 状态管理逻辑复杂(筛选、排序、批量操作)
- 样式开发耗时(响应式布局、深浅色切换)
- 代码规范难以统一(多人协作时尤其明显)
用AI辅助开发,可以把 70%的重复代码 交给AI处理,开发者专注于业务逻辑和架构设计。
1.2 技术栈选择
| 技术 | 选择理由 |
|---|---|
| React 19 | 支持新特性(Compiler、Server Components概念) |
| TypeScript 5.5 | 类型安全,减少AI生成代码的运行时错误 |
| Tailwind CSS 4.0 | AI生成样式代码最友好,CSS-in-JS容易被AI写崩 |
| Zustand | 状态管理简洁,AI生成代码质量高 |
| Cursor | 支持整个项目的AI协作,不是单文件补全 |
1.3 最终效果
我们最终会实现:
- 商品管理(列表、新增、编辑、删除)
- 订单管理(状态流转、筛选)
- 数据看板(图表、统计)
- 深浅色模式切换
- 响应式布局
二、环境准备:Cursor配置
2.1 安装与基础配置
bash
# 创建项目
npm create vite@latest admin-dashboard -- --template react-ts
cd admin-dashboard
npm install
# 安装依赖
npm install tailwindcss @tailwindcss/vite postcss autoprefixer
npm install zustand axios @tanstack/react-query
npm install lucide-react clsx tailwind-merge
npm install -D @types/node
2.2 Cursor配置文件
在项目根目录创建 .cursorrules 文件,这是让AI理解你项目规范的关键:
markdown
# 语言和框架
- 框架:React 19 + TypeScript 5.5
- 样式:Tailwind CSS 4.0
- 状态管理:Zustand
# 代码规范
- 组件使用 PascalCase(如 ProductList.tsx)
- Hooks 使用 camelCase,use开头(如 useProducts.ts)
- 工具函数使用 camelCase(如 formatCurrency.ts)
- 类型定义单独放在 types/ 目录
- 组件放在 components/ 目录,按功能模块分组
# Tailwind CSS 规范
- 使用 clsx + tailwind-merge 处理动态类名
- 颜色使用 CSS 变量,便于主题切换
- 响应式断点:sm(640px)、md(768px)、lg(1024px)、xl(1280px)
# AI 生成规范
- 生成代码必须包含完整的类型定义
- 组件必须添加 JSDoc 注释说明用途
- Hooks 必须标注依赖项和副作用
- 错误处理必须完整,不能 try-catch 后空着
# 禁止事项
- 禁止使用 any 类型(除非绝对必要)
- 禁止内联复杂样式逻辑
- 禁止生成重复的代码(先检查是否有可复用组件)
2.3 tsconfig.json 配置优化
json
{
"compilerOptions": {
"strict": true,
"noUncheckedIndexedAccess": true,
"exactOptionalPropertyTypes": true,
"noImplicitReturns": true,
"noFallthroughCasesInSwitch": true,
"baseUrl": ".",
"paths": {
"@/*": ["src/*"],
"@/components/*": ["src/components/*"],
"@/hooks/*": ["src/hooks/*"],
"@/stores/*": ["src/stores/*"],
"@/types/*": ["src/types/*"]
}
}
}
三、核心功能开发:手把手实操
3.1 商品列表页:AI生成组件
Step 1:用AI分析需求,生成技术方案
在Cursor中输入以下提示词:
plaintext
我正在开发一个电商后台管理系统的商品管理模块,需要你帮我分析需求并生成技术方案。
需求:
1. 商品列表展示(图片、名称、价格、评分、库存状态、上下架状态)
2. 筛选功能(按分类、价格区间、评分、上下架状态筛选)
3. 排序功能(按价格、评分、销量排序)
4. 分页功能(支持页码跳转和每页数量选择)
5. 批量操作(批量上下架、批量删除)
6. 响应式设计(桌面、平板、移动端适配)
请给出:
1. 组件拆分方案
2. 状态管理方案(哪些数据放Zustand,哪些放组件state)
3. API接口设计(模拟RESTful风格)
4. TypeScript类型定义
AI输出的技术方案:
typescript
Step 2:让AI生成商品卡片组件
继续在Cursor中输入:
plaintext
基于上面的类型定义,请帮我生成 ProductCard 组件。
要求:
1. 使用 Tailwind CSS 实现样式
2. 支持深色模式(使用 dark: 前缀)
3. 点击卡片跳转到编辑页
4. 悬停显示快捷操作按钮(上架/下架、编辑、删除)
5. 库存不足时显示红色警告
6. 图片使用懒加载,加载中显示骨架屏
7. 使用 useCartStore 管理收藏状态(zustand store)
请输出完整的组件代码,包含所有导入和类型定义。
AI生成的代码(关键部分):
tsx
// src/components/product/ProductCard.tsx
import React, { memo } from 'react';
import { Star, Edit2, Trash2, Eye, EyeOff } from 'lucide-react';
import { clsx } from 'clsx';
import type { Product } from '@/types/product';
import { useProductStore } from '@/stores/productStore';
interface ProductCardProps {
product: Product;
onEdit: (product: Product) => void;
onDelete: (product: Product) => void;
onToggleStatus: (product: Product) => void;
}
export const ProductCard = memo<ProductCardProps>(({
product,
onEdit,
onDelete,
onToggleStatus,
}) => {
const isLowStock = product.stock < 10;
const discount = Math.round(
((product.originalPrice - product.price) / product.originalPrice) * 100
);
return (
<div
className={clsx(
'group relative bg-white dark:bg-gray-800 rounded-xl shadow-sm',
'border border-gray-200 dark:border-gray-700',
'hover:shadow-lg transition-all duration-200',
'hover:border-blue-300 dark:hover:border-blue-600'
)}
>
{/* 图片区域 */}
<div className="relative aspect-square overflow-hidden rounded-t-xl bg-gray-100 dark:bg-gray-700">
<img
src={product.images[0] || '/placeholder.png'}
alt={product.name}
loading="lazy"
className="w-full h-full object-cover group-hover:scale-105 transition-transform duration-300"
/>
{/* 折扣标签 */}
{discount > 0 && (
<span className="absolute top-3 left-3 bg-red-500 text-white text-xs font-bold px-2 py-1 rounded-full">
-{discount}%
</span>
)}
{/* 状态标签 */}
<span
className={clsx(
'absolute top-3 right-3 text-xs font-medium px-2 py-1 rounded-full',
product.status === 'active'
? 'bg-green-100 text-green-700 dark:bg-green-900 dark:text-green-300'
: 'bg-gray-100 text-gray-600 dark:bg-gray-600 dark:text-gray-300'
)}
>
{product.status === 'active' ? '上架' : '下架'}
</span>
{/* 悬停操作按钮 */}
<div
className={clsx(
'absolute inset-0 bg-black/50 flex items-center justify-center gap-2',
'opacity-0 group-hover:opacity-100 transition-opacity'
)}
>
<button
onClick={() => onEdit(product)}
className="p-2 bg-white rounded-lg hover:bg-gray-100 transition-colors"
title="编辑"
>
<Edit2 className="w-5 h-5 text-gray-700" />
</button>
<button
onClick={() => onToggleStatus(product)}
className="p-2 bg-white rounded-lg hover:bg-gray-100 transition-colors"
title={product.status === 'active' ? '下架' : '上架'}
>
{product.status === 'active' ? (
<EyeOff className="w-5 h-5 text-gray-700" />
) : (
<Eye className="w-5 h-5 text-gray-700" />
)}
</button>
<button
onClick={() => onDelete(product)}
className="p-2 bg-red-500 rounded-lg hover:bg-red-600 transition-colors"
title="删除"
>
<Trash2 className="w-5 h-5 text-white" />
</button>
</div>
</div>
{/* 商品信息 */}
<div className="p-4">
<h3 className="text-lg font-semibold text-gray-900 dark:text-white line-clamp-2 mb-2">
{product.name}
</h3>
{/* 价格和评分 */}
<div className="flex items-center justify-between mb-2">
<div className="flex items-baseline gap-2">
<span className="text-xl font-bold text-red-500">
¥{product.price.toFixed(2)}
</span>
<span className="text-sm text-gray-400 line-through">
¥{product.originalPrice.toFixed(2)}
</span>
</div>
<div className="flex items-center gap-1">
<Star className="w-4 h-4 text-yellow-400 fill-yellow-400" />
<span className="text-sm text-gray-600 dark:text-gray-400">
{product.rating.toFixed(1)}
</span>
</div>
</div>
{/* 库存状态 */}
<div className="flex items-center justify-between text-sm">
<span className="text-gray-500 dark:text-gray-400">
销量 {product.salesCount}
</span>
{isLowStock ? (
<span className="text-red-500 font-medium">
库存不足 ({product.stock})
</span>
) : (
<span className="text-gray-500 dark:text-gray-400">
库存 {product.stock}
</span>
)}
</div>
</div>
</div>
);
});
ProductCard.displayName = 'ProductCard';
3.2 筛选面板:AI生成表单逻辑
提示词:
plaintext
请帮我生成一个商品筛选面板 FilterPanel 组件。
需求:
1. 支持关键词搜索(带防抖)
2. 分类选择器(使用 Select 组件)
3. 价格区间选择器(两个数字输入框)
4. 评分筛选(星级评分选择,最少0星)
5. 状态筛选(上架/下架/全部)
6. 一键重置筛选条件
7. 移动端折叠为抽屉模式
使用 React Hook Form 管理表单状态,
使用 Zod 进行表单验证(价格必须为正数,区间起始≤结束),
筛选变化时自动触发父组件的搜索。
AI生成的核心逻辑:
tsx
// src/components/product/FilterPanel.tsx
import React, { useEffect, useState, useCallback } from 'react';
import { useForm } from 'react-hook-form';
import { zodResolver } from '@hookform/resolvers/zod';
import { z } from 'zod';
import { Search, X, ChevronDown, ChevronUp } from 'lucide-react';
import { clsx } from 'clsx';
import type { ProductFilters, ProductCategory, ProductStatus } from '@/types/product';
// 表单验证 Schema
const filterSchema = z.object({
keyword: z.string().optional(),
category: z.string().optional(),
minPrice: z.coerce.number().min(0, '价格不能为负数').optional(),
maxPrice: z.coerce.number().min(0, '价格不能为负数').optional(),
minRating: z.coerce.number().min(0).max(5).optional(),
status: z.string().optional(),
}).refine(
(data) => {
if (data.minPrice !== undefined && data.maxPrice !== undefined) {
return data.minPrice <= data.maxPrice;
}
return true;
},
{ message: '起始价格不能大于结束价格', path: ['minPrice'] }
);
type FilterFormData = z.infer<typeof filterSchema>;
interface FilterPanelProps {
onFilterChange: (filters: ProductFilters) => void;
initialFilters?: ProductFilters;
}
export const FilterPanel: React.FC<FilterPanelProps> = ({
onFilterChange,
initialFilters = {},
}) => {
const [isExpanded, setIsExpanded] = useState(true);
const [debouncedKeyword, setDebouncedKeyword] = useState(initialFilters.keyword);
const {
register,
handleSubmit,
watch,
reset,
formState: { errors },
} = useForm<FilterFormData>({
resolver: zodResolver(filterSchema),
defaultValues: {
keyword: initialFilters.keyword || '',
category: initialFilters.category || '',
minPrice: initialFilters.priceRange?.[0],
maxPrice: initialFilters.priceRange?.[1],
minRating: initialFilters.minRating || 0,
status: initialFilters.status || '',
},
});
// 防抖处理关键词搜索
useEffect(() => {
const timer = setTimeout(() => {
setDebouncedKeyword(watch('keyword'));
}, 300);
return () => clearTimeout(timer);
}, [watch('keyword')]);
// 筛选变化时触发搜索
useEffect(() => {
const subscription = watch((_, { name }) => {
if (name !== 'keyword') {
handleFormSubmit(watch());
}
});
return () => subscription.unsubscribe();
}, [watch]);
// 关键词防抖后触发搜索
useEffect(() => {
const timer = setTimeout(() => {
onFilterChange({
...watch(),
keyword: debouncedKeyword,
});
}, 300);
return () => clearTimeout(timer);
}, [debouncedKeyword]);
const handleFormSubmit = useCallback((data: FilterFormData) => {
const filters: ProductFilters = {
keyword: data.keyword,
category: (data.category || '') as ProductCategory | '',
priceRange: data.minPrice && data.maxPrice
? [data.minPrice, data.maxPrice]
: undefined,
minRating: data.minRating,
status: (data.status || '') as ProductStatus | '',
};
onFilterChange(filters);
}, [onFilterChange]);
const handleReset = useCallback(() => {
reset();
onFilterChange({});
}, [reset, onFilterChange]);
// ... 渲染逻辑省略
};
3.3 状态管理:Zustand Store
提示词:
plaintext
请帮我生成商品管理的 Zustand Store。
需要管理的状态:
1. 商品列表(Product[])
2. 筛选条件(ProductFilters)
3. 分页信息(page, pageSize, total)
4. 加载状态(isLoading, isFetching)
5. 错误信息(error)
6. 选中的商品(用于批量操作)
需要实现的 Actions:
1. fetchProducts - 获取商品列表(调用 API)
2. setFilters - 更新筛选条件并重新获取
3. setPage / setPageSize - 分页操作
4. toggleProductStatus - 切换商品上下架状态
5. deleteProduct - 删除单个商品
6. batchDelete - 批量删除
7. batchToggleStatus - 批量切换状态
使用 @tanstack/react-query 处理数据请求和缓存,
Zustand 只负责 UI 状态管理。
AI生成的代码:
typescript
// src/stores/productStore.ts
import { create } from 'zustand';
import { devtools } from 'zustand/middleware';
import type { Product, ProductFilters, ProductListResponse } from '@/types/product';
import { productApi } from '@/api/product';
interface ProductState {
// 数据状态
products: Product[];
selectedIds: Set<string>;
// 筛选和分页
filters: ProductFilters;
pagination: {
page: number;
pageSize: number;
total: number;
totalPages: number;
};
// UI 状态
isLoading: boolean;
isFetching: boolean;
error: string | null;
// Actions
fetchProducts: () => Promise<void>;
setFilters: (filters: ProductFilters) => void;
setPage: (page: number) => void;
setPageSize: (pageSize: number) => void;
toggleProductStatus: (product: Product) => Promise<void>;
deleteProduct: (product: Product) => Promise<void>;
batchDelete: () => Promise<void>;
batchToggleStatus: () => Promise<void>;
toggleSelect: (id: string) => void;
selectAll: () => void;
clearSelection: () => void;
}
export const useProductStore = create<ProductState>()(
devtools(
(set, get) => ({
// 初始状态
products: [],
selectedIds: new Set(),
filters: {},
pagination: {
page: 1,
pageSize: 12,
total: 0,
totalPages: 0,
},
isLoading: false,
isFetching: false,
error: null,
// 获取商品列表
fetchProducts: async () => {
const { filters, pagination } = get();
set({ isFetching: true, error: null });
try {
const response = await productApi.getProducts({
...filters,
page: pagination.page,
pageSize: pagination.pageSize,
});
set({
products: response.items,
pagination: {
page: response.page,
pageSize: response.pageSize,
total: response.total,
totalPages: response.totalPages,
},
isFetching: false,
});
} catch (error) {
set({
error: error instanceof Error ? error.message : '获取商品列表失败',
isFetching: false,
});
}
},
// 更新筛选条件
setFilters: (filters) => {
set({ filters, pagination: { ...get().pagination, page: 1 } });
get().fetchProducts();
},
// 分页操作
setPage: (page) => {
set({ pagination: { ...get().pagination, page } });
get().fetchProducts();
},
setPageSize: (pageSize) => {
set({
pagination: { ...get().pagination, pageSize, page: 1 }
});
get().fetchProducts();
},
// 切换商品状态
toggleProductStatus: async (product) => {
const newStatus = product.status === 'active' ? 'inactive' : 'active';
// 乐观更新
set({
products: get().products.map((p) =>
p.id === product.id ? { ...p, status: newStatus } : p
),
});
try {
await productApi.updateProduct(product.id, { status: newStatus });
} catch (error) {
// 回滚
set({ products: get().products.map((p) =>
p.id === product.id ? product : p
)});
throw error;
}
},
// 删除商品
deleteProduct: async (product) => {
set({ isLoading: true });
try {
await productApi.deleteProduct(product.id);
set({
products: get().products.filter((p) => p.id !== product.id),
pagination: {
...get().pagination,
total: get().pagination.total - 1,
},
isLoading: false,
});
} catch (error) {
set({ isLoading: false });
throw error;
}
},
// 批量删除
batchDelete: async () => {
const { selectedIds } = get();
if (selectedIds.size === 0) return;
set({ isLoading: true });
try {
await productApi.batchDelete(Array.from(selectedIds));
set({
products: get().products.filter(
(p) => !selectedIds.has(p.id)
),
selectedIds: new Set(),
pagination: {
...get().pagination,
total: get().pagination.total - selectedIds.size,
},
isLoading: false,
});
} catch (error) {
set({ isLoading: false });
throw error;
}
},
// 批量切换状态
batchToggleStatus: async () => {
const { selectedIds, products } = get();
if (selectedIds.size === 0) return;
const selectedProducts = products.filter((p) => selectedIds.has(p.id));
const allActive = selectedProducts.every((p) => p.status === 'active');
const newStatus = allActive ? 'inactive' : 'active';
// 乐观更新
set({
products: get().products.map((p) =>
selectedIds.has(p.id) ? { ...p, status: newStatus } : p
),
});
try {
await productApi.batchUpdateStatus(
Array.from(selectedIds),
newStatus
);
} catch (error) {
// 回滚
set({ products });
throw error;
}
},
// 选择操作
toggleSelect: (id) => {
const newSelected = new Set(get().selectedIds);
if (newSelected.has(id)) {
newSelected.delete(id);
} else {
newSelected.add(id);
}
set({ selectedIds: newSelected });
},
selectAll: () => {
set({
selectedIds: new Set(get().products.map((p) => p.id)),
});
},
clearSelection: () => {
set({ selectedIds: new Set() });
},
}),
{ name: 'product-store' }
)
);
四、主题切换:深色模式实战
4.1 Tailwind CSS 深色模式配置
js
// tailwind.config.js
export default {
darkMode: 'class', // 使用 class 切换
content: ['./index.html', './src/**/*.{js,ts,jsx,tsx}'],
theme: {
extend: {
colors: {
primary: {
50: '#eff6ff',
100: '#dbeafe',
500: '#3b82f6',
600: '#2563eb',
900: '#1e3a8a',
},
},
},
},
plugins: [],
};
4.2 主题状态管理
typescript
// src/stores/themeStore.ts
import { create } from 'zustand';
import { persist } from 'zustand/middleware';
type Theme = 'light' | 'dark' | 'system';
interface ThemeState {
theme: Theme;
resolvedTheme: 'light' | 'dark';
setTheme: (theme: Theme) => void;
}
export const useThemeStore = create<ThemeState>()(
persist(
(set, get) => ({
theme: 'system',
resolvedTheme: 'light',
setTheme: (theme) => {
const root = document.documentElement;
if (theme === 'system') {
const systemDark = window.matchMedia('(prefers-color-scheme: dark)').matches;
root.classList.toggle('dark', systemDark);
set({ theme, resolvedTheme: systemDark ? 'dark' : 'light' });
} else {
root.classList.toggle('dark', theme === 'dark');
set({ theme, resolvedTheme: theme });
}
},
}),
{
name: 'theme-storage',
onRehydrateStorage: () => (state) => {
// 页面加载后同步主题
if (state) {
const root = document.documentElement;
if (state.theme !== 'system') {
root.classList.toggle('dark', state.theme === 'dark');
}
}
},
}
)
);
五、面试加分项:
5.1 性能优化
typescript
// 1. 使用 React.memo 避免不必要的重渲染
export const ProductCard = memo(ProductCardComponent, (prev, next) => {
return (
prev.product.id === next.product.id &&
prev.product.status === next.product.status &&
prev.product.stock === next.product.stock &&
prev.onEdit === next.onEdit // 比较回调引用
);
});
// 2. 虚拟列表(商品数量多时)
import { useVirtualizer } from '@tanstack/react-virtual';
const virtualizer = useVirtualizer({
count: products.length,
getScrollElement: () => parentRef.current,
estimateSize: () => 350,
overscan: 5,
});
// 3. 图片优化
<img
src={product.images[0]}
loading="lazy"
decoding="async"
srcSet={`${product.images[0]}?w=200 200w, ${product.images[0]}?w=400 400w`}
sizes="(max-width: 768px) 50vw, 25vw"
/>
5.2 错误边界
tsx
// src/components/ErrorBoundary.tsx
import React, { Component, type ErrorInfo, type ReactNode } from 'react';
interface Props {
children: ReactNode;
fallback?: ReactNode;
}
interface State {
hasError: boolean;
error: Error | null;
}
export class ErrorBoundary extends Component<Props, State> {
state: State = { hasError: false, error: null };
static getDerivedStateFromError(error: Error): State {
return { hasError: true, error };
}
componentDidCatch(error: Error, errorInfo: ErrorInfo) {
console.error('ErrorBoundary caught an error:', error, errorInfo);
// 可以上报到监控服务
}
render() {
if (this.state.hasError) {
return this.props.fallback || (
<div className="flex flex-col items-center justify-center h-full p-8">
<h2 className="text-xl font-bold text-red-500 mb-4">出错了</h2>
<p className="text-gray-600 dark:text-gray-400 mb-4">
{this.state.error?.message}
</p>
<button
onClick={() => this.setState({ hasError: false, error: null })}
className="px-4 py-2 bg-blue-500 text-white rounded-lg hover:bg-blue-600"
>
重试
</button>
</div>
);
}
return this.props.children;
}
}
六、延伸思考:AI编程时代的开发者进化
6.1 核心竞争力的转变
很多人担心"AI会取代程序员",但实际上:
plaintext
传统模式:写代码能力 → 核心竞争力
AI时代模式:
├── 系统设计能力(AI不擅长架构级设计)
├── 业务建模能力(把需求转化为可执行的设计)
├── 性能优化能力(AI生成的代码往往只关注功能)
└── 代码审查能力(识别AI代码中的问题)
6.2 AI编程的正确姿势
plaintext
❌ 错误做法:
1. 直接复制粘贴AI代码,不理解原理
2. 遇到问题就让AI解决,放弃思考
3. 不做代码审查,完全信任AI
✅ 正确做法:
1. AI生成代码后必须人工Review
2. 关键逻辑自己理解,必要时重写
3. 定期复盘:AI哪里写得好,哪里有问题
4. 建立自己的Prompt模板库
6.3 面试时如何展示AI编程能力
面试官真正想看到的是:
- 你有体系化的方法论(不是只会用Copilot补全)
- 你能驾驭AI而不是被AI驾驭(知道什么时候用AI,什么时候不用)
- 你有工程化思维(考虑性能、安全、可维护性)
- 你在持续进化(关注新工具、新趋势)
七、总结
今天这篇博客,我们:
- 搭建了完整的技术栈:Cursor + React + TypeScript + Tailwind CSS + Zustand
- 实现了核心功能:商品CRUD、筛选排序、批量操作、深色模式
- 展示了AI协作流程:需求分析 → 类型定义 → 组件生成 → 状态管理
- 补充了面试加分项:性能优化、错误边界、主题切换
- 沉淀了方法论:AI时代的开发流程和核心竞争力
这个项目放在GitHub上,面试时可以直接演示。比那些"手写一个Promise"的八股文项目有说服力多了。
项目结构:
plaintext
admin-dashboard/
├── src/
│ ├── components/
│ │ ├── common/ # 通用组件(Button, Input, Modal...)
│ │ └── product/ # 商品模块组件
│ ├── hooks/ # 自定义Hooks
│ ├── stores/ # Zustand状态管理
│ ├── types/ # TypeScript类型定义
│ ├── api/ # API请求封装
│ └── pages/ # 页面组件
├── .cursorrules # Cursor配置文件
└── package.json
有问题欢迎评论区交流,觉得有用的话点个赞~
更多推荐



所有评论(0)