| 版本 | 日期 | 描述 | 作者 |
|---|---|---|---|
| 1.0 | 2025-09-15 | 初始前端架构文档 | Winston |
项目已经使用了现代化的React全栈技术栈:
核心框架:
构建工具:
UI组件库:
状态管理:
路由系统:
API集成:
| 类别 | 技术 | 版本 | 用途 | 决策依据 |
|---|---|---|---|---|
| 框架 | React | 19.1.0 | 用户界面构建 | 最新版本,并发特性支持 |
| UI库 | shadcn/ui | - | 组件库和设计系统 | 基于Radix UI,无障碍支持 |
| 状态管理 | React Query | 5.83.0 | 服务端状态管理 | 数据同步、缓存、自动重试 |
| 路由 | React Router | 7.7.0 | 客户端路由 | 声明式路由,数据加载支持 |
| 构建工具 | Vite | 7.0.0 | 开发服务器和构建 | 快速冷启动,热重载支持 |
| 样式方案 | Tailwind CSS | 4.1.11 | 原子化CSS框架 | 实用优先,设计一致性 |
| 类型安全 | TypeScript | 5.8.3 | 类型检查 | 编译时错误检测 |
| API客户端 | Hono RPC | 4.8.5 | 类型安全API调用 | 前后端统一类型定义 |
| 表单处理 | React Hook Form | 7.61.1 | 表单管理和验证 | 高性能,最小重渲染 |
| 图标库 | Lucide React | 0.536.0 | 图标系统 | 简洁一致的图标设计 |
| 测试框架 | Vitest + Testing Library | - | 组件测试 | 现代化测试工具,更好的TypeORM支持 |
d8d-starter/
├── src/
│ ├── client/ # 前端代码根目录
│ │ ├── admin/ # 管理后台界面
│ │ │ ├── components/ # 管理后台专属组件
│ │ │ ├── pages/ # 管理页面
│ │ │ ├── layouts/ # 管理后台布局
│ │ │ └── hooks/ # 管理后台专属hooks
│ │ ├── home/ # 用户主页界面
│ │ │ ├── components/ # 主页专属组件
│ │ │ ├── pages/ # 主页页面
│ │ │ └── hooks/ # 主页专属hooks
│ │ ├── components/ # 共享UI组件
│ │ │ └── ui/ # 基础UI组件 (shadcn/ui)
│ │ │ ├── button.tsx
│ │ │ ├── input.tsx
│ │ │ └── ...
│ │ ├── hooks/ # 共享React Hooks
│ │ │ └── use-mobile.ts # 移动端检测hook
│ │ ├── lib/ # 工具库
│ │ │ └── utils.ts # className工具函数 (cn)
│ │ ├── utils/ # 工具函数目录
│ │ │ ├── utils.ts # 通用工具函数
│ │ │ ├── logger.ts # 日志工具
│ │ │ └── ClientOnly.tsx # 客户端渲染组件
│ │ └── api.ts # API客户端配置
│ ├── server/ # 后端代码
│ └── shared/ # 前后端共享代码
├── public/ # 静态资源
├── dist/ # 构建输出
└── docs/ # 文档
import * as React from "react"
import { cva, type VariantProps } from "class-variance-authority"
import { cn } from "@/client/lib/utils"
const componentVariants = cva(
"base-styles transition-all duration-200",
{
variants: {
variant: {
default: "bg-primary text-primary-foreground",
secondary: "bg-secondary text-secondary-foreground",
destructive: "bg-destructive text-destructive-foreground",
outline: "border border-input bg-background",
ghost: "hover:bg-accent hover:text-accent-foreground",
},
size: {
default: "h-9 px-4 py-2",
sm: "h-8 rounded-md px-3 text-xs",
lg: "h-10 rounded-md px-8",
icon: "size-9",
},
},
defaultVariants: {
variant: "default",
size: "default",
},
}
)
export interface ComponentProps
extends React.HTMLAttributes<HTMLDivElement>,
VariantProps<typeof componentVariants> {
isLoading?: boolean
disabled?: boolean
}
const Component = React.forwardRef<HTMLDivElement, ComponentProps>(
({ className, variant, size, isLoading = false, disabled = false, ...props }, ref) => {
return (
<div
ref={ref}
className={cn(
componentVariants({ variant, size, className }),
isLoading && "opacity-50 cursor-not-allowed",
disabled && "pointer-events-none opacity-50"
)}
aria-disabled={disabled || isLoading}
{...props}
/>
)
}
)
Component.displayName = "Component"
export { Component, componentVariants }
文件命名:
PascalCase.tsx (如: UserProfile.tsx)camelCase.ts (如: formatDate.ts)useCamelCase.ts (如: useUserData.ts)camelCase.ts (如: apiTypes.ts)组件命名:
PascalCase (如: UserCard)ComponentProps (如: ButtonProps)ComponentVariants (如: ButtonVariants)基于对现有代码的分析,项目采用混合状态管理模式:
React Context + React Query组合:
src/
├── client/
│ ├── admin/
│ │ └── hooks/
│ │ └── AuthProvider.tsx # 管理后台认证上下文
│ ├── home/
│ │ └── hooks/
│ │ └── AuthProvider.tsx # 用户前台认证上下文
│ ├── hooks/
│ │ └── use-mobile.ts # 共享移动端检测hook
│ └── api.ts # API客户端配置
项目采用简单的React Query使用模式:
配置方式: 在每个入口文件独立创建QueryClient
// 在入口文件中创建QueryClient
const queryClient = new QueryClient({
defaultOptions: {
queries: {
retry: 1,
refetchOnWindowFocus: false,
},
},
})
查询模式: 直接在组件中使用useQuery
// 数据查询示例
const { data: usersData, isLoading, refetch } = useQuery({
queryKey: ['users', searchParams],
queryFn: async () => {
const res = await userClient.$get({ query: searchParams })
if (res.status !== 200) throw new Error('获取失败')
return await res.json()
}
})
数据变更模式: 直接调用API客户端 + 手动refetch
// 数据变更示例(未使用useMutation)
const handleCreateSubmit = async (data: CreateUserFormData) => {
try {
const res = await userClient.$post({ json: data })
if (res.status !== 201) throw new Error('创建失败')
toast.success('创建成功')
refetch() // 手动重新获取数据
} catch (error) {
console.error('操作失败:', error)
toast.error('操作失败,请重试')
}
}
项目使用自定义的axios适配器实现Hono RPC客户端:
// 自定义axios适配器 (src/client/api.ts)
const axiosFetch = async (url: RequestInfo | URL, init?: RequestInit) => {
const requestHeaders: Record<string, string> = {};
if (init?.headers instanceof Headers) {
init.headers.forEach((value, key) => {
requestHeaders[key] = value;
})
}
const response = await axios.request({
url: url.toString(),
method: init?.method || 'GET',
headers: requestHeaders,
data: init?.body,
}).catch((error) => {
if (isAxiosError(error)) {
return {
status: error.response?.status,
statusText: error.response?.statusText,
data: error.response?.data,
headers: error.response?.headers
}
}
throw error;
})
const responseHeaders = new Headers();
if (response.headers) {
for (const [key, value] of Object.entries(response.headers)) {
responseHeaders.set(key, value);
}
}
// 处理204 No Content响应
const body = response.status === 204
? null
: responseHeaders.get('content-type')?.includes('application/json')
? JSON.stringify(response.data)
: response.data;
return new Response(
body,
{
status: response.status,
statusText: response.statusText,
headers: responseHeaders
}
)
}
// Hono客户端实例导出
export const authClient = hc<AuthRoutes>('/', {
fetch: axiosFetch,
}).api.v1.auth;
export const userClient = hc<UserRoutes>('/', {
fetch: axiosFetch,
}).api.v1.users;
export const roleClient = hc<RoleRoutes>('/', {
fetch: axiosFetch,
}).api.v1.roles;
后端使用OpenAPIHono定义路由类型:
// 服务器端路由定义 (src/server/api.ts)
const userRoutes = api.route('/api/v1/users', usersRouter)
const authRoutes = api.route('/api/v1/auth', authRoute)
const roleRoutes = api.route('/api/v1/roles', rolesRoute)
export type AuthRoutes = typeof authRoutes
export type UserRoutes = typeof userRoutes
export type RoleRoutes = typeof roleRoutes
在组件中直接使用类型安全的客户端:
// 使用Hono RPC客户端
const response = await userClient.$get({
query: { page: 1, pageSize: 10, keyword: 'search' }
})
if (response.status === 200) {
const data = await response.json()
// 类型安全的data
}
双路由系统:
/admin/* (位于 src/client/admin/routes.tsx)/* (位于 src/client/home/routes.tsx)路由结构:
// 管理后台路由
- /admin/login → 登录页面
- /admin → 重定向到仪表板 (保护路由)
- /admin/dashboard → 仪表板页面 (保护路由)
- /admin/users → 用户管理页面 (保护路由)
// 用户前台路由
- / → 首页
- /login → 登录页面
- /register → 注册页面
- /member → 会员页面 (保护路由)
export const ProtectedRoute = ({ children }: { children: React.ReactNode }) => {
const { isAuthenticated, isLoading } = useAuth();
const navigate = useNavigate();
useEffect(() => {
if (!isLoading && !isAuthenticated) {
navigate('/admin/login', { replace: true });
}
}, [isAuthenticated, isLoading, navigate]);
if (isLoading) {
return <div>Loading...</div>;
}
if (!isAuthenticated) {
return null;
}
return children;
};
基于CSS自定义属性的主题系统:
:root {
--background: oklch(1 0 0);
--foreground: oklch(0.145 0 0);
--primary: oklch(0.205 0 0);
--primary-foreground: oklch(0.985 0 0);
/* ... 更多设计token */
}
.dark {
--background: oklch(0.145 0 0);
--foreground: oklch(0.985 0 0);
--primary: oklch(0.985 0 0);
/* ... 暗色主题变量 */
}
测试框架: Vitest + Testing Library
测试位置: __tests__文件夹与源码并列
覆盖率目标: 核心组件80%+覆盖率
describe('Button', () => {
it('renders correctly with default variant', () => {
render(<Button>Click me</Button>)
expect(screen.getByRole('button')).toHaveTextContent('Click me')
})
it('calls onClick handler when clicked', () => {
const handleClick = vi.fn()
render(<Button onClick={handleClick}>Click me</Button>)
fireEvent.click(screen.getByRole('button'))
expect(handleClick).toHaveBeenCalledTimes(1)
})
})
# 实际环境变量配置(基于服务器端配置)
OSS_BASE_URL=https://oss.d8d.fun
APP_NAME=多八多Aider
常用命令:
npm run dev # 启动开发服务器
npm run build # 生产构建
npm run test # 运行测试
文档状态: 正式版 下次评审: 2025-10-15 架构师: Winston 🏗️