PMFlow/frontend/PMFLOW_IMPLEMENTATION.md

250 lines
7.7 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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 | `VITE_API_BASE_URL` 或开发时 `/api`(代理)、生产时 `http://localhost:8000` |
| JWT 自动附加 | `request` 拦截器从 `localStorage.pmflow_token` 读取并设置 `Authorization: Bearer <token>` |
| 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`,未登录则 `<Navigate to="/login?redirect=...">`
- 登录页 `/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`
- 通过 `<html class="light">` / `<html class="dark">` 切换
- Tailwind 使用 `darkMode: ["class"]` 与 CSS 变量
### 3.6 布局 (`src/components/layout/`)
- **Layout**:flex 布局,左侧 Sidebar、右侧 TopBar + `<Outlet />` 主区域
- **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`)
- 环境变量需配置 `VITE_API_BASE_URL` 指向实际 API 地址
### 6.4 环境变量
`.env.example` 示例:
```
VITE_API_BASE_URL=http://localhost:8000
```
若后端挂载在 `/api`,可设置为:`VITE_API_BASE_URL=http://your-domain/api`
---
## 七、实现要点小结
| 点 | 技术 / 手段 |
|----|-------------|
| 路由守卫 | `AuthGuard` + `useAuthStore().token` + `<Navigate>` |
| 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),定义信号→宪章→任务→执行→发布→复盘的强制绑定与版本链。