# PMFlow 前端实现总结 > 项目路径:`./frontend` > 技术栈:Vite + React 18 + TypeScript + shadcn/ui + Tailwind CSS --- ## 一、技术选型 | 类别 | 技术 | 版本 | 用途 | |------|------|------|------| | 构建 | Vite | 5.x | 开发服务器、打包 | | 框架 | React | 18.3 | UI 渲染 | | 语言 | TypeScript | 5.6 | 类型安全 | | 路由 | React Router | 6.28 | 路由、导航守卫 | | 请求 | TanStack Query | 5.x | 服务端状态、缓存、错误处理 | | 请求 | Axios | 1.7 | HTTP 客户端 | | 状态 | Zustand | 4.5 | 全局状态(含持久化) | | UI | shadcn/ui | - | 组件库(Radix UI 底座) | | 样式 | Tailwind CSS | 3.4 | 原子化 CSS | | 图表 | Recharts | 2.13 | 图表 | | 图标 | Lucide React | 0.46 | 图标 | | 工具 | clsx + tailwind-merge | - | 类名合并 | --- ## 二、项目目录结构 ``` frontend/ ├── public/ │ └── vite.svg ├── src/ │ ├── components/ │ │ ├── layout/ │ │ │ ├── Layout.tsx # 主布局(侧栏 + 顶栏 + 主区域) │ │ │ ├── Sidebar.tsx # 侧边栏(导航、暗色模式切换) │ │ │ └── TopBar.tsx # 顶栏(用户头像、通知、退出) │ │ ├── ui/ # shadcn/ui 组件 │ │ │ ├── button.tsx │ │ │ ├── card.tsx │ │ │ ├── input.tsx │ │ │ ├── dialog.tsx │ │ │ ├── table.tsx │ │ │ └── dropdown-menu.tsx │ │ └── ThemeInit.tsx # 暗色模式初始化 │ ├── hooks/ │ │ └── useTheme.ts # 主题切换 Hook │ ├── lib/ │ │ ├── api.ts # Axios 实例 + 拦截器 │ │ └── utils.ts # cn() 等工具 │ ├── pages/ │ │ ├── LoginPage.tsx │ │ ├── DashboardPage.tsx # 团队负载看板 │ │ ├── ProjectsPage.tsx │ │ ├── ProjectBoardPage.tsx │ │ ├── CharterDetailPage.tsx │ │ ├── SkillsPage.tsx │ │ └── UserProfilePage.tsx │ ├── router/ │ │ └── index.tsx # 路由配置 + 路由守卫 │ ├── stores/ │ │ └── auth.ts # 登录态(Zustand + 持久化) │ ├── index.css # Tailwind + CSS 变量 │ ├── main.tsx │ └── vite-env.d.ts ├── .env.example ├── .gitignore ├── components.json # shadcn 配置 ├── Dockerfile ├── nginx.conf ├── package.json ├── postcss.config.js ├── tailwind.config.js ├── tsconfig.json └── vite.config.ts ``` --- ## 三、核心实现 ### 3.1 Axios 封装 (`src/lib/api.ts`) | 能力 | 实现方式 | |------|----------| | 基础 URL | 浏览器固定请求同源 `/api`;开发由 Vite proxy 转发,生产由 nginx `/api` 反代 | | JWT 自动附加 | `request` 拦截器从 `localStorage.pmflow_token` 读取并设置 `Authorization: Bearer ` | | 401 处理 | `response` 拦截器:清除 token,重定向到 `/login?redirect=<当前路径>` | | 错误提示 | 解析 `detail`(字符串或数组),统一 `console.error` 并 `Promise.reject` | ### 3.2 TanStack Query 配置 (`src/main.tsx`) - `retry: 1` - `refetchOnWindowFocus: false` - `mutations.onError`:全局错误日志 ### 3.3 路由与守卫 (`src/router/index.tsx`) - 使用 `createBrowserRouter` 配置路由 - `AuthGuard`:检查 `useAuthStore().token`,未登录则 `` - 登录页 `/login` 为公开路由,其余均需认证 **路由表:** | 路径 | 说明 | |------|------| | `/login` | 登录页 | | `/dashboard` | 团队负载看板 | | `/projects` | 项目列表 | | `/projects/:id/board` | 项目看板 | | `/projects/:id/charter/:charterId` | 需求宪章详情 | | `/skills` | 人员能力管理 | | `/users/:id/profile` | 个人中心 | ### 3.4 认证状态 (`src/stores/auth.ts`) - Zustand + `persist`,存储到 `localStorage`(`pmflow-auth`) - 字段:`token`、`user`(含 `id`、`username`、`name`、`email`、`role`) - 方法:`setAuth`、`logout`、`isAuthenticated` ### 3.5 暗色模式 (`src/hooks/useTheme.ts` + `ThemeInit.tsx`) - 主题存于 `localStorage.pmflow_theme`,可选 `light` / `dark` - 默认跟随 `prefers-color-scheme` - 通过 `` / `` 切换 - Tailwind 使用 `darkMode: ["class"]` 与 CSS 变量 ### 3.6 布局 (`src/components/layout/`) - **Layout**:flex 布局,左侧 Sidebar、右侧 TopBar + `` 主区域 - **Sidebar**:PMFlow logo、`NavLink` 导航、暗色模式按钮 - **TopBar**:通知图标、用户头像下拉(个人中心、退出登录) ### 3.7 Vite 代理 (`vite.config.ts`) ```ts proxy: { "/api": { target: "http://localhost:8000", changeOrigin: true, rewrite: (path) => path.replace(/^\/api/, ""), // /api/auth/login -> /auth/login }, } ``` 前端请求 `/api/xxx` 会被代理到 `http://localhost:8000/xxx`。 --- ## 四、页面与 API 对接 ### 4.1 登录页 (`LoginPage.tsx`) - 表单:`username`、`password` - 请求:`POST /auth/login`,`Content-Type: application/x-www-form-urlencoded` - 响应:`{ access_token, token_type, user }`,写入 `useAuthStore`,再 `navigate(redirect)` - 已登录时直接跳转到 `redirect` 或 `/dashboard` ### 4.2 团队负载看板 (`DashboardPage.tsx`) - 请求:`GET /skills/workload/team` - 后端返回:`{ items: WorkloadItem[], total: number }`,前端取 `items` 或兼容数组 - 图表:Recharts 横向柱状图(工时) - 表格:成员、任务数、工时 ### 4.3 能力管理页 (`SkillsPage.tsx`) - 请求:`GET /skills/tags` - 表格展示:名称、分类、描述 ### 4.4 其他页面 - `ProjectsPage`、`ProjectBoardPage`、`CharterDetailPage`、`UserProfilePage`:占位结构,待对接 API --- ## 五、shadcn/ui 组件 基于 Radix UI + Tailwind,已引入: - **Button**:多种 variant / size - **Card**:CardHeader、CardTitle、CardDescription、CardContent、CardFooter - **Input**:基础输入框 - **Dialog**:弹窗 - **Table**:TableHeader、TableBody、TableRow、TableCell 等 - **DropdownMenu**:下拉菜单 工具函数 `cn()` 来自 `@/lib/utils`,用于合并 `clsx` 与 `tailwind-merge`。 --- ## 六、部署 ### 6.1 本地开发 ```bash cd frontend npm install npm run dev ``` - 前端:`http://localhost:5173` - API 代理到:`http://localhost:8000` ### 6.2 生产构建 ```bash npm run build ``` 输出目录:`dist/` ### 6.3 Docker - 多阶段构建:Node 构建 → nginx 静态托管 - `nginx.conf` 中 `/api` 代理到后端服务(如 `http://backend:8000`) - 浏览器侧不再配置后端绝对地址,避免本机地址、公网地址和生产地址分叉 ### 6.4 环境变量 `.env.example` 示例: ``` VITE_DEV_API_PROXY_TARGET=http://127.0.0.1:8001 ``` `VITE_DEV_API_PROXY_TARGET` 只给 Vite dev server 使用;浏览器请求始终是同源 `/api`。 --- ## 七、实现要点小结 | 点 | 技术 / 手段 | |----|-------------| | 路由守卫 | `AuthGuard` + `useAuthStore().token` + `` | | JWT 持久化 | Zustand persist + localStorage | | API 401 处理 | Axios response 拦截器 + 重定向登录 | | 暗色模式 | class 模式 + CSS 变量 + `useTheme` | | 开发代理 | Vite proxy `/api` → 后端 | | 响应结构兼容 | `{ items: [] }` 与数组统一处理为 `Array.isArray` | | 路径别名 | `@/` → `./src/` | | 类型声明 | `vite-env.d.ts` 中引用 `vite/client` | --- ## 八、相关架构 - **SSTC 唯一事实链路**:见 [`docs/SSTC_ARCHITECTURE.md`](../docs/SSTC_ARCHITECTURE.md),定义信号→宪章→任务→执行→发布→复盘的强制绑定与版本链。