9dd2d4a1fc8ec31f402d249396426ba9ec6a41bd
- 侧边栏最大宽度从 800px 扩展至 1200px(含动态计算) - 文件树最小宽度从 180px 提升至 300px - 文件树最大宽度扩展为侧边栏宽度 - 预览关闭时文件树自动占满侧边栏,分隔线隐藏 - 外部拖动手柄改为与内部分隔线一致的样式(1px、hover 变色)
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: 添加技能详情页面
- 新增技能详情布局
- 集成版本历史展示
快速开始
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等)
│ ├── workspace/ # 工作空间组件 (WorkspaceSidebar, FileTree, FileTreeItem, FileContextMenu, FilePreviewModal)
│ ├── Layout.jsx # 主布局组件(sidebar + content)
│ └── ListSelector.jsx # 列表选择器
│
├── contexts/ # 全局状态 (UserContext)
├── services/ # 数据访问层 (api.js)
├── data/ # 模拟数据
│ ├── adminData.js # 管理台数据(含平台级模型配置)
│ ├── projectModelConfigs.js # 项目级模型配置数据
│ ├── userModelConfigs.js # 个人模型配置数据
│ └── workspace.js # 工作空间文件数据
│
├── pages/ # 页面组件
│ ├── console/ # 工作台子页面
│ ├── admin/ # 管理台子页面
│ └── developer/ # 开发台子页面
│
├── styles/ # 样式系统(五层架构)
│ ├── tokens/ # 设计令牌(颜色、间距等)
│ ├── core/ # 核心样式(重置、CSS变量)
│ ├── layouts/ # 布局系统
│ ├── components/ # 组件样式
│ ├── pages/ # 页面样式
│ │ ├── model-selector/ # 模型选择器样式
│ │ └── ...
│ └── global.scss # 主入口
│
├── App.jsx # 路由配置
└── main.jsx # 应用入口
核心模块
| 模块 | 路由 | 功能 |
|---|---|---|
| 首页 | / |
品牌展示、登录入口 |
| 工作台 | /console |
聊天、技能市场、定时任务、项目管理(成员/权限/技能/模型配置)、个人模型配置 |
| 管理台 | /admin |
部门/用户/项目管理、模型配置 |
| 开发台 | /developer |
技能开发、版本管理 |
样式规范
五层架构
tokens → core → layouts → components → pages
| 层级 | 职责 | 规则 |
|---|---|---|
| tokens/ | 设计令牌 | 禁止硬编码颜色、间距、字号 |
| core/ | 核心样式 | CSS重置、全局变量 |
| layouts/ | 布局系统 | 页面骨架,不含具体组件 |
| components/ | 组件样式 | 可复用,BEM命名 |
| pages/ | 页面样式 | 页面独有,不放置可复用样式 |
BEM命名规范
// 格式: .block__element--modifier
.card { }
.card__header { }
.card__body { }
.btn--primary { }
.tag--running { }
// JSX对应
<div className="card">
<div className="card__header">标题</div>
</div>
<button className="btn btn--primary">确认</button>
<span className="tag tag--running">运行中</span>
引用规则
// 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 |
表格操作列规范
// 表头
<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>
禁止内联样式定义操作列
组件规范
新增组件流程
- 创建
src/components/{category}/ComponentName.jsx - 创建
src/styles/components/{name}/_index.scss - 使用
@use '../../tokens' as *; - 遵循BEM命名
- 在
components/_index.scss添加@forward '{name}';
组件模板
// 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;
// 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 |
可展开侧边栏导航组 |
| WorkspaceSidebar | components/workspace/WorkspaceSidebar.jsx |
工作空间侧边栏 |
| FileTree | components/workspace/FileTree.jsx |
文件树组件 |
| FileTreeItem | components/workspace/FileTreeItem.jsx |
文件树项组件 |
| FileContextMenu | components/workspace/FileContextMenu.jsx |
文件右键菜单 |
| FilePreviewModal | components/workspace/FilePreviewModal.jsx |
文件预览弹窗 |
路由规范
嵌套路由结构
所有页面通过正式路由导航,使用 HashRouter + 嵌套路由。
// 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/addvs/admin/departments/:id/edit
子页面参数获取
// 使用 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>;
}
新增页面流程
- 在
App.jsx添加路由定义 - 创建页面组件(使用
useParams/useNavigate) - 在对应 Layout 的 sidebar 添加导航项
- 确保页面返回按钮使用固定路径导航
状态管理
全局状态:UserContext
// 使用
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 访问:
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 });
// 工作空间
api.workspace.getFiles(); // 获取工作空间文件树
api.workspace.getFileById(id); // 根据 ID 获取文件
api.workspace.getFileIcon(fileType); // 获取文件图标
api.workspace.getFileType(filename); // 根据文件名获取文件类型
开发清单
新增功能前检查
- 是否可复用已有样式?
- 是否可复用已有组件?
- 是否可复用已有页面布局模式?
代码提交前检查
- 类名是否遵循BEM?
- 颜色/间距是否使用tokens变量?
- 表格操作列是否使用规范类名?
pnpm build是否通过?- 文档注释是否使用中文?
- 变量/函数命名是否使用英文?
已知问题
| 问题 | 说明 | 处理 |
|---|---|---|
| 构建警告 | inlineDynamicImports is deprecated |
来自vite-plugin-singlefile,可忽略 |
| 浏览器兼容 | 不支持IE | 使用现代浏览器 |
对话输入框布局
布局结构
对话输入框采用响应式水平布局,根据屏幕宽度自动调整组件排列方式。
桌面端(>= 768px):
- 水平布局:模型选择器 → 输入区域 → 工具按钮 → 发送按钮
- 模型选择器:标准模式(显示完整模型名称,宽度 160px)
- 分隔方式:右侧垂直边框
平板端(480-768px):
- 水平布局:模型选择器 → 输入区域 → 工具按钮 → 发送按钮
- 模型选择器:紧凑模式(仅显示图标,宽度 100px)
- 工具按钮:上传文件、代码块
- 分隔方式:右侧垂直边框
移动端(< 480px):
- 垂直布局:模型选择器在上,输入区域在下
- 模型选择器:标准模式(显示完整模型名称,宽度 160px)
- 分隔方式:底部水平边框
ModelSelector 组件
ModelSelector 组件支持 variant 属性,控制显示模式:
<ModelSelector
selectedModel={selectedModel}
onSelectModel={setSelectedModel}
variant="standard" // 或 "compact"
/>
variant 属性值:
standard:标准模式,显示完整模型名称compact:紧凑模式,仅显示图标
响应式断点
| 断点名称 | 屏幕宽度 | 布局模式 | 模型选择器 | 宽度 |
|---|---|---|---|---|
| 桌面端 | >= 768px | 水平 | 标准 | 160px |
| 平板端 | 480-768px | 水平 | 紧凑 | 100px |
| 移动端 | < 480px | 垂直 | 标准 | 160px |
工具按钮
- 工具按钮横向排列在输入区域右侧(水平布局)或下方(垂直布局)
- 当前工具按钮:上传文件、代码块
- 工具按钮右侧显示分隔边框(水平布局)
下拉菜单方向
- 默认向下展开
- 如果下方空间不足,自动向上展开
最后更新:2026-04-10
Description
Languages
JavaScript
79.9%
SCSS
20%