- 新增 SidebarNavGroup 组件支持可展开导航组 - 路由从 /console/projects 调整为 /console/project/* - 成员管理页面独立为子菜单 - 新增权限配置、技能配置占位页面 - URL 驱动展开状态,刷新保持 - 更新 README.md 和 specs
445 lines
12 KiB
Markdown
445 lines
12 KiB
Markdown
# GrandClaw 原型项目
|
||
|
||
企业级AI智能助手平台前端原型,展示主要页面布局、交互流程和视觉设计。
|
||
|
||
---
|
||
|
||
## ⚠️ 核心约束(必读)
|
||
|
||
> 以下约束必须严格遵守,不得违反
|
||
|
||
### 项目性质
|
||
|
||
- **纯前端展示原型**:无后端交互,供内部开发人员参考UI界面
|
||
- **目标**:展示页面布局、样式和组件能力
|
||
- **交互限制**:允许轻量级交互展示(表单验证、弹框),重叠/覆盖类状态(弹框、下拉)允许简单交互切换
|
||
|
||
### 语言规范
|
||
|
||
| 场景 | 语言 |
|
||
|------|------|
|
||
| 交流、文档、注释、提交信息 | **中文** |
|
||
| 代码命名(变量、函数、类、文件) | **英文** |
|
||
|
||
### 技术约束
|
||
|
||
| 约束项 | 规则 |
|
||
|--------|------|
|
||
| UI库 | **禁止引入**,使用当前SCSS样式方案 |
|
||
| TypeScript | **禁止引入**,使用JavaScript |
|
||
| ESLint | **禁止引入** |
|
||
| 包管理器 | **pnpm**(不允许npm、yarn) |
|
||
| 测试 | **不构建**,使用`pnpm build`验证打包即可 |
|
||
| 性能优化 | **不做**,保持vite-plugin-singlefile单文件打包 |
|
||
| 安全防御 | **不做**,eval/dangerouslySetInnerHTML按需使用 |
|
||
|
||
### 复用优先原则
|
||
|
||
```
|
||
新增代码时优先级:
|
||
1. 复用已有样式(src/styles)
|
||
2. 复用已有组件(src/components)
|
||
3. 复用已有页面布局模式
|
||
4. 最后才创建新代码
|
||
```
|
||
|
||
### Git提交规范
|
||
|
||
```
|
||
格式: 类型: 简短描述
|
||
|
||
类型可选:
|
||
- feat: 新功能
|
||
- fix: 修复
|
||
- refactor: 重构
|
||
- docs: 文档
|
||
- style: 格式
|
||
- test: 测试
|
||
- chore: 构建/工具
|
||
|
||
多行描述: 空行后加详细说明
|
||
|
||
示例:
|
||
feat: 添加技能详情页面
|
||
|
||
- 新增技能详情布局
|
||
- 集成版本历史展示
|
||
```
|
||
|
||
---
|
||
|
||
## 快速开始
|
||
|
||
```bash
|
||
pnpm install # 安装依赖
|
||
pnpm build # 验证打包(不运行pnpm dev,会挂起流程)
|
||
```
|
||
|
||
---
|
||
|
||
## 技术栈
|
||
|
||
| 技术 | 版本 | 用途 |
|
||
|------|------|------|
|
||
| React | 19.2.4 | UI框架 |
|
||
| React Router (HashRouter) | 7.13.1 | 路由管理 |
|
||
| Vite | 8.0.1 | 构建工具 |
|
||
| react-icons | 5.5.0 | 图标库 |
|
||
| Sass | 1.98.0 | 样式预处理 |
|
||
| vite-plugin-singlefile | 2.3.2 | 单文件打包 |
|
||
|
||
---
|
||
|
||
## 项目结构
|
||
|
||
```
|
||
src/
|
||
├── components/ # 组件库
|
||
│ ├── common/ # 通用组件 (Modal, Toast, EmptyState等)
|
||
│ ├── layout/ # 布局组件 (AppHeader, AppLayout, UserDropdown, ConsoleLayout, AdminLayout, DeveloperLayout等)
|
||
│ ├── Layout.jsx # 主布局组件(sidebar + content)
|
||
│ └── ListSelector.jsx # 列表选择器
|
||
│
|
||
├── contexts/ # 全局状态 (UserContext)
|
||
├── services/ # 数据访问层 (api.js)
|
||
├── data/ # 模拟数据
|
||
│
|
||
├── pages/ # 页面组件
|
||
│ ├── console/ # 工作台子页面
|
||
│ ├── admin/ # 管理台子页面
|
||
│ └── developer/ # 开发台子页面
|
||
│
|
||
├── styles/ # 样式系统(五层架构)
|
||
│ ├── tokens/ # 设计令牌(颜色、间距等)
|
||
│ ├── core/ # 核心样式(重置、CSS变量)
|
||
│ ├── layouts/ # 布局系统
|
||
│ ├── components/ # 组件样式
|
||
│ ├── pages/ # 页面样式
|
||
│ └── global.scss # 主入口
|
||
│
|
||
├── App.jsx # 路由配置
|
||
└── main.jsx # 应用入口
|
||
```
|
||
|
||
---
|
||
|
||
## 核心模块
|
||
|
||
| 模块 | 路由 | 功能 |
|
||
|------|------|------|
|
||
| 首页 | `/` | 品牌展示、登录入口 |
|
||
| 工作台 | `/console` | 聊天、技能市场、定时任务、项目管理(成员/权限/技能配置) |
|
||
| 管理台 | `/admin` | 部门/用户/项目管理、模型配置 |
|
||
| 开发台 | `/developer` | 技能开发、版本管理 |
|
||
|
||
---
|
||
|
||
## 样式规范
|
||
|
||
### 五层架构
|
||
|
||
```
|
||
tokens → core → layouts → components → pages
|
||
```
|
||
|
||
| 层级 | 职责 | 规则 |
|
||
|------|------|------|
|
||
| tokens/ | 设计令牌 | **禁止硬编码**颜色、间距、字号 |
|
||
| core/ | 核心样式 | CSS重置、全局变量 |
|
||
| layouts/ | 布局系统 | 页面骨架,不含具体组件 |
|
||
| components/ | 组件样式 | 可复用,**BEM命名** |
|
||
| pages/ | 页面样式 | 页面独有,不放置可复用样式 |
|
||
|
||
### BEM命名规范
|
||
|
||
```scss
|
||
// 格式: .block__element--modifier
|
||
.card { }
|
||
.card__header { }
|
||
.card__body { }
|
||
.btn--primary { }
|
||
.tag--running { }
|
||
```
|
||
|
||
```jsx
|
||
// JSX对应
|
||
<div className="card">
|
||
<div className="card__header">标题</div>
|
||
</div>
|
||
<button className="btn btn--primary">确认</button>
|
||
<span className="tag tag--running">运行中</span>
|
||
```
|
||
|
||
### 引用规则
|
||
|
||
```scss
|
||
// tokens层:无依赖
|
||
@use 'colors' as *;
|
||
|
||
// 其他层:依赖tokens
|
||
@use '../tokens' as *; // core、layouts
|
||
@use '../../tokens' as *; // components
|
||
@use '../tokens' as *; // pages
|
||
```
|
||
|
||
**禁止跨层引用**(如components引用pages)
|
||
|
||
### 按钮规范
|
||
|
||
| 场景 | 类名 |
|
||
|------|------|
|
||
| 主操作 | `btn btn--primary` |
|
||
| 次要操作 | `btn` |
|
||
| 表格内编辑 | `text-btn text-btn-primary` |
|
||
| 表格内删除 | `text-btn text-btn-danger` |
|
||
| 危险确认 | `btn btn--danger` |
|
||
| 警告操作 | `btn btn--warning` |
|
||
|
||
### 表格操作列规范
|
||
|
||
```jsx
|
||
// 表头
|
||
<th className="col-actions">操作</th> // 200px
|
||
<th className="col-actions--narrow">操作</th> // 120px
|
||
|
||
// 单元格
|
||
<td className="col-actions">
|
||
<div className="table-actions">
|
||
<button className="text-btn text-btn-primary">编辑</button>
|
||
<button className="text-btn text-btn-danger">删除</button>
|
||
</div>
|
||
</td>
|
||
```
|
||
|
||
**禁止内联样式定义操作列**
|
||
|
||
---
|
||
|
||
## 组件规范
|
||
|
||
### 新增组件流程
|
||
|
||
1. 创建 `src/components/{category}/ComponentName.jsx`
|
||
2. 创建 `src/styles/components/{name}/_index.scss`
|
||
3. 使用 `@use '../../tokens' as *;`
|
||
4. 遵循BEM命名
|
||
5. 在 `components/_index.scss` 添加 `@forward '{name}';`
|
||
|
||
### 组件模板
|
||
|
||
```jsx
|
||
// components/common/Example.jsx
|
||
function Example({ title, children }) {
|
||
return (
|
||
<div className="example">
|
||
<div className="example__header">{title}</div>
|
||
<div className="example__body">{children}</div>
|
||
</div>
|
||
);
|
||
}
|
||
export default Example;
|
||
```
|
||
|
||
```scss
|
||
// styles/components/example/_index.scss
|
||
@use '../../tokens' as *;
|
||
|
||
.example {
|
||
padding: $spacing-4;
|
||
|
||
&__header {
|
||
font-weight: $font-weight-semibold;
|
||
color: $text-1;
|
||
}
|
||
|
||
&__body {
|
||
color: $text-2;
|
||
}
|
||
}
|
||
```
|
||
|
||
### 可复用组件
|
||
|
||
| 组件 | 路径 | 用途 |
|
||
|------|------|------|
|
||
| Layout | `components/Layout.jsx` | 主布局(sidebar + content) |
|
||
| AppLayout | `components/layout/AppLayout.jsx` | 全局布局(header + main) |
|
||
| AppHeader | `components/layout/AppHeader.jsx` | 统一导航头部 |
|
||
| UserDropdown | `components/layout/UserDropdown.jsx` | 用户下拉菜单 |
|
||
| Modal | `components/common/Modal.jsx` | 确认弹窗 |
|
||
| Toast | `components/common/Toast.jsx` | 消息提示 |
|
||
| EmptyState | `components/common/EmptyState.jsx` | 空状态展示 |
|
||
| SearchBar | `components/common/SearchBar.jsx` | 搜索框 |
|
||
| StatusBadge | `components/common/StatusBadge.jsx` | 状态标签 |
|
||
| SidebarNavItem | `components/layout/SidebarNavItem.jsx` | 侧边栏导航项 |
|
||
| SidebarNavGroup | `components/layout/SidebarNavGroup.jsx` | 可展开侧边栏导航组 |
|
||
|
||
---
|
||
|
||
## 路由规范
|
||
|
||
### 嵌套路由结构
|
||
|
||
所有页面通过正式路由导航,使用 HashRouter + 嵌套路由。
|
||
|
||
```jsx
|
||
// App.jsx
|
||
<HashRouter>
|
||
<Routes>
|
||
<Route path="/login" element={<LoginPage />} />
|
||
<Route element={<AppLayout />}>
|
||
<Route path="/" element={<HomePage />} />
|
||
|
||
<Route path="/console" element={<ConsoleLayout />}>
|
||
<Route index element={<Navigate to="chat/welcome" replace />} />
|
||
<Route path="chat" element={<ChatPage />} />
|
||
<Route path="chat/:scene" element={<ChatPage />} />
|
||
<Route path="skills" element={<SkillsPage />} />
|
||
<Route path="skills/:skillId" element={<SkillDetailPage />} />
|
||
<Route path="project" element={<Navigate to="members" replace />} />
|
||
<Route path="project/members" element={<MembersPage />} />
|
||
<Route path="project/members/add" element={<AddMemberPage />} />
|
||
<Route path="project/members/:memberId/config" element={<MemberConfigPage />} />
|
||
<Route path="project/permissions" element={<PermissionsPage />} />
|
||
<Route path="project/skills" element={<SkillsConfigPage />} />
|
||
{/* ...更多子路由 */}
|
||
</Route>
|
||
|
||
<Route path="/admin" element={<AdminLayout />}>
|
||
<Route index element={<Navigate to="overview" replace />} />
|
||
<Route path="overview" element={<OverviewPage />} />
|
||
<Route path="departments" element={<DepartmentsPage />} />
|
||
<Route path="departments/add" element={<AddDepartmentPage />} />
|
||
<Route path="departments/:id/edit" element={<AddDepartmentPage />} />
|
||
{/* ...更多子路由 */}
|
||
</Route>
|
||
|
||
<Route path="/developer" element={<DeveloperLayout />}>
|
||
<Route index element={<Navigate to="overview" replace />} />
|
||
{/* ...子路由 */}
|
||
</Route>
|
||
</Route>
|
||
</Routes>
|
||
</HashRouter>
|
||
```
|
||
|
||
**说明**:
|
||
- `AppLayout` 包裹所有需要统一 Header 的页面
|
||
- 登录页独立,不使用 `AppLayout`
|
||
- 每个模块(Console/Admin/Developer)使用独立的 Layout 组件包裹 `<Outlet />`
|
||
- 模块根路径自动重定向到默认子页面
|
||
- 新增/编辑表单通过 URL 参数区分:`/admin/departments/add` vs `/admin/departments/:id/edit`
|
||
|
||
### 子页面参数获取
|
||
|
||
```jsx
|
||
// 使用 useParams 获取 URL 参数
|
||
function SkillDetailPage() {
|
||
const { skillId } = useParams();
|
||
const skill = api.skills.getById(Number(skillId));
|
||
// ...
|
||
}
|
||
|
||
// 使用 useNavigate 进行导航
|
||
function SkillsPage() {
|
||
const navigate = useNavigate();
|
||
return <button onClick={() => navigate('/console/skills/1')}>查看</button>;
|
||
}
|
||
```
|
||
|
||
### 新增页面流程
|
||
|
||
1. 在 `App.jsx` 添加路由定义
|
||
2. 创建页面组件(使用 `useParams` / `useNavigate`)
|
||
3. 在对应 Layout 的 sidebar 添加导航项
|
||
4. 确保页面返回按钮使用固定路径导航
|
||
|
||
---
|
||
|
||
## 状态管理
|
||
|
||
### 全局状态:UserContext
|
||
|
||
```jsx
|
||
// 使用
|
||
import { useUserContext } from '../contexts/UserContext.jsx';
|
||
|
||
function Component() {
|
||
const { user } = useUserContext();
|
||
return <div>{user.name}</div>;
|
||
}
|
||
```
|
||
|
||
### 页面状态由 URL 驱动
|
||
|
||
所有页面状态(当前页面、场景名、实体 ID)通过 URL 参数驱动,不依赖 localStorage。
|
||
|
||
| 状态类型 | 驱动方式 | 示例 |
|
||
|---------|---------|------|
|
||
| 当前页面 | URL 路径 | `/console/skills` |
|
||
| 实体 ID | URL 参数 | `/console/skills/:skillId` |
|
||
| 场景名 | URL 参数 | `/console/chat/:scene` |
|
||
| 新增/编辑模式 | URL 参数有无 | `/admin/users/add` vs `/admin/users/:id/edit` |
|
||
| 编辑数据 | `api.getById(Number(id))` | 通过 ID 重新获取 |
|
||
|
||
---
|
||
|
||
## 数据访问
|
||
|
||
所有数据通过 `src/services/api.js` 访问:
|
||
|
||
```javascript
|
||
import api from '../services/api.js';
|
||
|
||
// 用户
|
||
api.user.getInfo();
|
||
|
||
// 技能
|
||
api.skills.list();
|
||
api.skills.getById(id);
|
||
|
||
// 开发台
|
||
api.developer.getMySkills();
|
||
api.developer.getOverview();
|
||
|
||
// 管理台
|
||
api.admin.departments.list();
|
||
api.admin.users.list();
|
||
api.admin.modelConfigs.create(data);
|
||
|
||
// 日志筛选
|
||
api.logs.filter({ user, type, status });
|
||
```
|
||
|
||
---
|
||
|
||
## 开发清单
|
||
|
||
### 新增功能前检查
|
||
|
||
- [ ] 是否可复用已有样式?
|
||
- [ ] 是否可复用已有组件?
|
||
- [ ] 是否可复用已有页面布局模式?
|
||
|
||
### 代码提交前检查
|
||
|
||
- [ ] 类名是否遵循BEM?
|
||
- [ ] 颜色/间距是否使用tokens变量?
|
||
- [ ] 表格操作列是否使用规范类名?
|
||
- [ ] `pnpm build` 是否通过?
|
||
- [ ] 文档注释是否使用中文?
|
||
- [ ] 变量/函数命名是否使用英文?
|
||
|
||
---
|
||
|
||
## 已知问题
|
||
|
||
| 问题 | 说明 | 处理 |
|
||
|------|------|------|
|
||
| 构建警告 | `inlineDynamicImports is deprecated` | 来自vite-plugin-singlefile,可忽略 |
|
||
| 浏览器兼容 | 不支持IE | 使用现代浏览器 |
|
||
|
||
---
|
||
|
||
*最后更新:2026-03-30*
|