Files
GrandClaw-prototype/README.md
lanyuanxiaoyao f1d5e77285 refactor: 重构样式系统为五层分层架构
- 建立 tokens/core/layouts/components/pages 五层样式架构
- 所有组件采用 BEM 命名规范(.block__element--modifier)
- 16 个组件目录,每个组件独立 _index.scss
- 清理表格操作列内联样式,统一使用 .col-actions/.table-actions
- 更新 README 样式开发规范文档
- 同步 3 个 delta spec 到主 specs

Co-Authored-By: opencode <noreply@opencode.ai>
2026-03-26 14:14:52 +08:00

1005 lines
34 KiB
Markdown
Raw 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.
# GrandClaw 原型项目
## 项目概述
GrandClaw 是一个企业级AI智能助手平台的前端原型项目主要用于展示平台的主要页面布局、交互流程和视觉设计。项目采用现代化的前端技术栈实现了四大核心模块
- **首页**:平台入口,包含登录功能
- **工作台**用户与AI助手交互的主要界面包含聊天、技能市场、日志查询、定时任务等功能
- **管理台**:运营管理界面,包含总览、部门管理、用户管理、项目管理
- **开发台**:技能开发界面,包含我的技能、技能编辑、开发文档等
## 技术栈
### 核心框架
- **React 19.2.4**UI组件库
- **React Router 7.13.1**路由管理使用HashRouter
- **Vite 8.0.1**:构建工具和开发服务器
### UI与样式
- **react-icons 5.5.0**图标库Feather + FontAwesome图标集
- **Sass 1.98.0**CSS预处理器
- **五层分层架构**tokens → core → layouts → components → pages
- **BEM 命名规范**:所有组件类名遵循 `.block__element--modifier` 格式
### 构建优化
- **vite-plugin-singlefile 2.3.2**单文件打包解决CORS问题
- **相对路径配置**支持直接打开HTML文件运行
- **注意**:构建时会显示弃用警告 `WARN inlineDynamicImports option is deprecated, please use codeSplitting: false instead.`,这是由 `vite-plugin-singlefile` 插件内部使用弃用选项导致的,不影响功能,可以忽略。
### 包管理
- **pnpm**:高效的包管理器
## 项目结构
```
grandclaw-archtype/
├── dist/ # 构建输出目录单个index.html
├── public/ # 静态资源
├── src/ # 源代码
│ ├── App.jsx # 主路由配置
│ ├── main.jsx # 应用入口
│ ├── components/ # 组件
│ │ ├── layout/ # 布局组件SidebarBrand、SidebarUser、SidebarNavItem
│ │ ├── common/ # 通用UI组件EmptyState、StatusBadge、TagInput、SearchBar
│ │ ├── Layout.jsx # 通用布局组件(侧边栏+主内容)
│ │ └── ListSelector.jsx # 列表选择器组件(支持单选/多选)
│ ├── contexts/ # 全局状态管理
│ │ └── UserContext.jsx # 用户信息上下文
│ ├── hooks/ # 自定义Hook
│ │ ├── useLocalStorage.js # localStorage状态管理Hook
│ │ ├── usePageState.js # 页面状态持久化Hook
│ │ └── useNavigation.js # 导航逻辑Hook
│ ├── constants/ # 常量定义
│ │ ├── pages.js # 页面配置(路由、标题、图标)
│ │ └── storageKeys.js # localStorage键名常量
│ ├── services/ # 数据访问层
│ │ └── api.js # 统一数据访问接口
│ ├── data/ # 模拟数据
│ │ ├── conversations.js # 聊天场景数据
│ │ ├── developerData.js # 开发台数据
│ │ ├── logs.js # 日志数据
│ │ ├── members.js # 成员数据
│ │ ├── skills.js # 技能数据
│ │ ├── tasks.js # 定时任务数据
│ │ ├── adminData.js # 管理台数据(部门/用户/项目/总览/日志/模型配置)
│ │ └── configTypes.js # 模型配置类型注册表
│ ├── pages/ # 页面组件
│ │ ├── HomePage.jsx # 首页
│ │ ├── LoginPage.jsx # 登录页面
│ │ ├── ConsolePage.jsx # 工作台主页面
│ │ ├── AdminPage.jsx # 管理台主页面
│ │ ├── DeveloperPage.jsx # 开发台主页面
│ │ ├── console/ # 工作台子页面
│ │ ├── admin/ # 管理台子页面
│ │ └── developer/ # 开发台子页面
│ └── styles/ # SCSS样式系统分层架构
│ ├── tokens/ # 第1层设计令牌
│ │ ├── _colors.scss # 品牌色、功能色、中性色
│ │ ├── _spacing.scss # 间距系统4px基数
│ │ ├── _shadows.scss # 阴影层级
│ │ ├── _radius.scss # 圆角系统
│ │ ├── _typography.scss # 字体、字号、字重
│ │ ├── _z-index.scss # 层级系统
│ │ ├── _transitions.scss # 过渡动画参数
│ │ ├── _breakpoints.scss # 响应式断点
│ │ ├── _mixins.scss # 可复用混入
│ │ └── _index.scss # 统一导出
│ ├── core/ # 第2层核心样式
│ │ ├── _reset.scss # CSS重置
│ │ ├── _css-variables.scss # :root CSS变量
│ │ ├── _base.scss # body全局样式
│ │ └── _index.scss
│ ├── layouts/ # 第3层布局系统
│ │ ├── _app-shell.scss # 主框架sidebar+header+main
│ │ ├── _chat-layout.scss # 聊天页面布局
│ │ ├── _admin-layout.scss # 管理台布局
│ │ └── _index.scss
│ ├── components/ # 第4层组件库
│ │ ├── button/ # 按钮(.btn, .btn--primary, .btn--ghost
│ │ ├── card/ # 卡片(.card, .card__header, .card__body
│ │ ├── table/ # 表格(.table, .table-actions, .col-actions
│ │ ├── form/ # 表单(.form-group, .form__input, .form__label
│ │ ├── tag/ # 标签(.tag--running, .tag--admin
│ │ ├── modal/ # 弹窗(.modal, .modal__overlay
│ │ ├── toast/ # 提示(.toast--success, .toast--error
│ │ ├── pagination/ # 分页
│ │ ├── empty-state/ # 空状态
│ │ ├── switch/ # 开关
│ │ ├── skill-card/ # 技能卡片
│ │ ├── nav/ # 导航项
│ │ ├── detail/ # 详情页组件
│ │ ├── password-input/ # 密码输入框
│ │ ├── search-bar/ # 搜索栏
│ │ ├── stat-card/ # 统计卡片
│ │ └── _index.scss # 统一导出
│ ├── pages/ # 第5层页面特有样式
│ │ ├── _console.scss # 工作台特有组件
│ │ ├── _admin.scss # 管理台特有组件
│ │ ├── _developer.scss # 开发台特有组件
│ │ ├── _home.scss # 首页特有组件
│ │ └── _index.scss
│ └── global.scss # 主入口文件(仅 @use无直接样式
├── index.html # HTML入口文件
├── package.json # 项目配置和依赖
├── vite.config.js # Vite配置
└── pnpm-lock.yaml # 依赖锁定文件
```
## 开发指南
### 环境要求
- Node.js 18+
- pnpm推荐或 npm
### 安装依赖
```bash
pnpm install
```
### 开发模式
```bash
pnpm dev
# 访问 http://localhost:5173
```
### 构建生产版本
```bash
pnpm build
# 生成 dist/index.html单个HTML文件
```
### 预览生产构建
直接双击 `dist/index.html` 文件,或在浏览器中打开。
## 核心功能
### 1. 首页
- 平台品牌展示
- 导航入口(工作台、开发台、管理台)
- 登录入口
### 2. 登录页面
- 用户名/邮箱登录
- 密码输入
- 验证码(防爆破)
- 记住我功能
- 忘记密码链接
### 3. 工作台Console
- **聊天界面**:支持多种聊天场景(欢迎页、普通对话、技能调用、文件上传)
- **技能市场**:浏览已上架技能、订阅技能(仅展示已上架技能)
- **我的技能**:管理已订阅技能,支持启用/禁用、配置变量、取消订阅NEW
- **技能配置**:为已订阅技能配置 key-value 变量NEW
- **日志查询**:支持按用户、类型、状态筛选
- **定时任务**:管理定时任务,支持启用/禁用,查看任务详情
- **项目管理**:成员列表,增加成员
- **账号管理**:个人信息和密码修改
### 4. 管理台Admin
- **运营总览**:平台运营指标卡片(用户总数、部门数量、项目数量、今日调用)、异常/待办事项提醒、最近操作日志
- **审核管理**:版本审核列表与详情、下架审核列表与详情
- **部门管理**:部门列表,支持搜索筛选、新增、编辑、启用/禁用、删除确认
- **用户管理**:用户列表,支持搜索筛选(关键词/部门/状态)、新增、编辑、启用/禁用、删除确认,角色区分(管理员/开发者/成员)
- **项目管理**:项目列表,支持搜索筛选、新增、编辑、启用/禁用、删除确认
- **模型配置**管理平台的默认模型接入配置支持多组配置OpenAI兼容接口、智算管理平台等类型可切换生效配置生效中配置不可编辑/删除,配置类型创建后不可修改
- **日志查询**:全局系统日志查询,支持多维度筛选(关键词、用户、部门、类型、状态、时间范围)
### 5. 开发台Developer
- **总览**:开发者指标卡片(我的技能总数、已上架、开发中、待审核)、待审核项目列表、最近动态
- **我的技能**:技能列表,支持关键词搜索(内部名称/内部描述)、状态筛选(开发中/已上架/下架审核中/已下架)、分页,支持下架(需要先撤回审核中的版本)、删除(已上架需要先下架),仅展示开发者内部信息
- **技能详情**:四段式布局 - 1) 概览卡片内部信息内部名称、状态、编辑按钮2) 当前生效版本卡片商店展示效果预览分类作为第一个标签显示3) 版本历史卡片普通卡片布局展示版本号、状态、日期、版本说明、发布信息预览、操作按钮无下载按钮4) 管理操作卡片
- **创建技能**:简化表单 - 仅内部技能名称、内部技能描述(明确标注仅供开发者管理使用)
- **编辑内部信息**:独立页面编辑内部技能名称/内部技能描述(明确标注不影响商店展示)
- **上传新版本**:增强表单 - 版本说明区域 + 发布信息区域(技能发布名称、技能发布描述、分类、标签、图标),非首版本默认继承当前生效版本的值
- **开发文档**:技能开发相关文档
- **开发者设置**:开发者账号信息
#### 重要数据结构变更说明
- **开发者内部信息**:内部名称、内部描述 - 仅供开发者管理,与商店展示完全无关,可随时修改无需审核
- **版本发布信息**:发布名称、发布描述、分类、标签、图标 - 存储在版本中,随版本审核通过后生效,修改必须发布新版本
- **技能商店展示**:完全从当前生效版本取发布信息,确保任何商店内容变更都经过版本审核
- **分类与标签展示**:分类始终作为第一个标签显示,与普通标签一起展示
#### 按钮禁用规则
基于 `hasPendingReview` 标志和技能状态控制操作按钮可用性:
- **上传新版本按钮**`status === 'unlisting' || status === 'unlisted' || hasPendingReview === true` 时禁用
- **下架技能按钮**`hasPendingReview === true` 时禁用
- **删除技能按钮**`status === 'published' || status === 'unlisting' || hasPendingReview === true` 时禁用
#### 撤回审核按钮样式
- **按钮类型**:警告按钮(橙色)
- **按钮类名**`btn btn-warning btn-sm`
- **按钮图标**逆时针旋转图标FiRotateCcw
- **按钮文案**"撤回审核"
- **展示位置**:版本历史卡片中审核中版本的操作区域
## 路由结构
项目使用 **HashRouter**所有路由基于哈希路径支持直接打开HTML文件运行。
### 主要路由
```
/ # 首页
/login # 登录页面
/console # 工作台
/admin # 管理台
/developer # 开发台
```
### 路由配置App.jsx
```jsx
import { HashRouter as Router, Routes, Route } from 'react-router-dom';
<Router>
<Routes>
<Route path="/" element={<HomePage />} />
<Route path="/login" element={<LoginPage />} />
<Route path="/console" element={<ConsolePage />} />
<Route path="/admin" element={<AdminPage />} />
<Route path="/developer" element={<DeveloperPage />} />
</Routes>
</Router>
```
## 通用组件
### 布局组件
#### SidebarBrand 品牌区域
侧边栏顶部的品牌展示组件。
```jsx
import { SidebarBrand } from '../components/layout/SidebarBrand.jsx';
<SidebarBrand subtitle="企业级AI平台" />
```
#### SidebarUser 用户信息
侧边栏底部的用户信息展示组件,使用全局用户上下文。
```jsx
import { SidebarUser } from '../components/layout/SidebarUser.jsx';
<SidebarUser
onClick={() => navigate('/account')}
wrapperClassName="sidebar-user"
infoClassName="sidebar-user-info"
nameClassName="sidebar-user-name"
roleClassName="sidebar-user-role"
/>
```
#### SidebarNavItem 导航项
侧边栏导航菜单项组件。
```jsx
import { SidebarNavItem } from '../components/layout/SidebarNavItem.jsx';
<SidebarNavItem
icon={<FiMessageSquare />}
label="智能助手"
active={currentPage === 'chat'}
onClick={() => setCurrentPage('chat')}
itemClassName="sidebar-nav-item"
iconClassName="sidebar-nav-icon"
textClassName="sidebar-nav-text"
/>
```
### 通用UI组件
#### EmptyState 空状态
用于展示空列表或无数据状态的组件。
```jsx
import EmptyState from '../components/common/EmptyState.jsx';
<EmptyState
icon={<FiInbox size={48} />}
title="暂无数据"
description="当前没有可显示的内容"
/>
```
#### Modal 弹窗
用于展示确认操作的弹窗组件,支持自定义标题和内容。
```jsx
import Modal from '../components/common/Modal.jsx';
<Modal
visible={showModal}
title="确认删除"
onConfirm={handleConfirm}
onCancel={handleCancel}
confirmText="删除"
>
确定要删除这个任务吗
</Modal>
```
#### Toast 消息提示
用于展示操作结果的消息提示组件,支持成功、错误、警告、信息四种类型。
```jsx
import Toast from '../components/common/Toast.jsx';
<Toast
visible={showToast}
type="success"
message="操作成功"
onClose={() => setShowToast(false)}
/>
```
**支持的类型:**
- `success` - 成功(绿色)
- `error` - 错误(红色)
- `warning` - 警告(黄色)
- `info` - 信息(蓝色)
#### StatusBadge 状态标签
用于显示状态(成功、失败、警告等)的标签组件。
```jsx
import StatusBadge from '../components/common/StatusBadge.jsx';
<StatusBadge status="success" text="运行中" />
<StatusBadge status="error" text="失败" />
<StatusBadge status="warning" text="警告" />
<StatusBadge status="stopped" text="已停止" />
```
#### TagInput 标签输入
支持输入标签的输入框组件。
```jsx
import TagInput from '../components/common/TagInput.jsx';
<TagInput
tags={tags}
onChange={setTags}
placeholder="输入标签后按回车添加"
/>
```
#### SearchBar 搜索框
通用的搜索输入框组件。
```jsx
import SearchBar from '../components/common/SearchBar.jsx';
<SearchBar
value={searchQuery}
onChange={setSearchQuery}
placeholder="搜索..."
/>
```
### 列表组件
#### ListSelector 列表选择器
通用的列表选择器组件,支持单选和多选模式。
```jsx
import ListSelector from '../components/ListSelector.jsx';
<ListSelector
data={dataList} // 数据数组
selectedIds={selectedIds} // 已选项
onChange={handleChange} // 选择变化回调
searchPlaceholder="搜索..." // 搜索框占位符
columns={columns} // 列配置 [{ key, label }]
multiSelect={false} // 是否多选
selectedLabel="已选标签" // 已选标签文字
onClearSelected={() => {}} // 清除已选回调
/>
```
## 状态管理
### 全局状态
#### UserContext 用户信息上下文
全局用户信息状态,通过 `UserProvider` 提供给整个应用。
```jsx
import { UserProvider, useUserContext } from '../contexts/UserContext.jsx';
// 在 App.jsx 中包裹
<UserProvider user={{ name: '张三', avatar: '张', role: 'AI 产品部' }}>
<App />
</UserProvider>
// 在组件中使用
function Component() {
const { user } = useUserContext();
return <div>{user.name}</div>;
}
```
### 自定义 Hooks
#### usePageState 页面状态持久化
处理页面切换状态的 Hook支持 localStorage 持久化和主页跳转重置。
```javascript
import { usePageState } from '../hooks/usePageState.js';
import { CONSOLE_PAGES } from '../constants/pages.js';
import { CONSOLE_KEYS } from '../constants/storageKeys.js';
const { currentPage, setCurrentPage } = usePageState({
storageKey: CONSOLE_KEYS.CURRENT_PAGE,
defaultPage: 'chat',
pageTitles: CONSOLE_PAGES,
});
```
#### useNavigation 导航逻辑
封装页面导航逻辑的 Hook支持携带额外数据。
```javascript
import { useNavigation } from '../hooks/useNavigation.js';
const { navigateToPage, extraData } = useNavigation(setCurrentPage);
// 导航到指定页面
navigateToPage('skills', { skillId: '1' });
// 获取导航传递的数据
const skillId = extraData.skillId;
```
#### useLocalStorage localStorage 状态管理
同步组件状态到 localStorage 的 Hook。
```javascript
import { useLocalStorage } from '../hooks/useLocalStorage.js';
const [value, setValue] = useLocalStorage('myKey', 'defaultValue');
setValue('newValue'); // 自动同步到 localStorage
```
### 导航状态持久化策略
每个主要页面(工作台、管理台、开发台)都有独立的`localStorage`键:
```javascript
// 工作台
localStorage.setItem('console_currentPage', 'chat');
localStorage.setItem('console_currentScene', 'welcome');
// 管理台
localStorage.setItem('admin_currentPage', 'overview');
// 开发台
localStorage.setItem('developer_currentPage', 'overview');
localStorage.setItem('developer_currentSkillId', '1');
```
### 主页跳转 vs 刷新浏览器
通过`location.state.fromHome`区分两种导航来源:
```javascript
// 从主页跳转:显示默认页面
useEffect(() => {
if (location.state?.fromHome) {
setCurrentPage('chat'); // 默认页面
navigate('.', { replace: true, state: {} }); // 清除state
}
}, [location.state]);
// 刷新浏览器从localStorage恢复
const [currentPage, setCurrentPage] = useState(() => {
return localStorage.getItem('console_currentPage') || 'chat';
});
```
## 样式系统
### 分层架构
样式采用五层架构,依赖方向自上而下:`tokens → core → layouts → components → pages`
```
┌─────────────────────────────────────────┐
│ pages/ 页面特有组件,仅覆盖模式 │ ← 最上层
├─────────────────────────────────────────┤
│ components/ 可复用UI组件库BEM命名 │
├─────────────────────────────────────────┤
│ layouts/ 布局系统shell/chat/admin
├─────────────────────────────────────────┤
│ core/ 重置、CSS变量、body样式 │
├─────────────────────────────────────────┤
│ tokens/ 设计令牌(颜色/间距/阴影... │ ← 最底层
└─────────────────────────────────────────┘
```
### 第1层Tokens设计令牌
所有设计决策集中定义,禁止在组件或页面中硬编码值。
```scss
// src/styles/tokens/_colors.scss
$primary: #3B82F6;
$primary-dark: #2563EB;
$success: #10B981;
$danger: #EF4444;
$text-1: #1E293B;
$text-3: #94A3B8;
// src/styles/tokens/_spacing.scss
$spacing-1: 4px;
$spacing-2: 8px;
$spacing-4: 16px;
// src/styles/tokens/_typography.scss
$font-size-base: 14px;
$font-weight-semibold: 600;
```
**使用方式:** 通过 `@use` 引入,直接使用变量名。
```scss
@use '../../tokens' as *;
.my-component {
color: $text-1;
padding: $spacing-4;
font-weight: $font-weight-semibold;
}
```
**同时提供 CSS 变量**core 层定义),供运行时和 JSX 内联场景使用:
```scss
// core/_css-variables.scss 自动从 tokens 生成 :root 变量
:root {
--color-primary: #{$primary};
--color-text-1: #{$text-1};
}
// JSX 中可通过 style 使用
<div style={{ color: 'var(--color-text-1)' }}>
```
### 第2层Core核心样式
包含 CSS 重置、`:root` CSS 变量定义、body 全局样式。无业务逻辑。
```
core/
├── _reset.scss # * { margin:0; padding:0; box-sizing:border-box; }
├── _css-variables.scss # :root { --color-primary: ...; }
├── _base.scss # body { font-family, color, background }
└── _index.scss # @forward 所有 core 模块
```
### 第3层Layouts布局系统
页面骨架布局不包含具体UI组件样式。
| 文件 | 职责 |
|------|------|
| `_app-shell.scss` | 主框架sidebar + header + main-content + 响应式 |
| `_chat-layout.scss` | 聊天页chat-sidebar + chat-content + conversation-list |
| `_admin-layout.scss` | 管理台admin-sidebar + admin-nav + member-selection |
**关键类名:**
- `.layout` / `.app-shell` — 主布局容器
- `.sidebar` — 侧边栏
- `.main-content` — 主内容区
- `.header` — 顶部栏
- `.page-content` — 页面内容区
### 第4层Components组件库
每个组件一个目录,内部 `_index.scss` 包含该组件的完整样式。
```
components/
├── button/_index.scss
├── card/_index.scss
├── table/_index.scss
├── form/_index.scss
├── tag/_index.scss
├── modal/_index.scss
├── toast/_index.scss
└── _index.scss # @forward 所有组件
```
**组件目录创建规则:**
```
components/
└── {component-name}/
└── _index.scss # 必须存在,包含组件全部样式
```
### 第5层Pages页面特有样式
仅包含**页面独有的组件样式**,不放可复用组件。
```
pages/
├── _console.scss # 聊天消息、欢迎区、输入框等
├── _admin.scss # 总览统计、异常列表等
├── _developer.scss # 技能概览卡片、版本历史卡片等
├── _home.scss # 首页英雄区、特性卡片等
└── _index.scss
```
### 主入口文件
`global.scss` 是纯入口文件,**仅包含 `@use` 语句**,无任何直接样式定义:
```scss
// src/styles/global.scss
@use 'tokens' as *;
@use 'core' as *;
@use 'layouts' as *;
@use 'components' as *;
@use 'pages/console' as *;
@use 'pages/admin' as *;
@use 'pages/developer' as *;
@use 'pages/home' as *;
```
---
## 样式开发规范
### BEM 命名规范
所有组件类名必须遵循 BEM 格式:`.block__element--modifier`
```scss
// Block — 组件根节点
.card { }
// Element — 组件内部元素,用双下划线连接
.card__header { }
.card__body { }
.card__footer { }
// Modifier — 变体/状态,用双连字符连接
.card--flat { }
.card--elevated { }
.btn--primary { }
.btn--danger { }
.tag--running { }
.tag--admin { }
```
**JSX 对应写法:**
```jsx
<div className="card">
<div className="card__header">
<span className="card__title">标题</span>
</div>
<div className="card__body">
内容
</div>
</div>
<button className="btn btn--primary">确认</button>
<button className="btn btn--danger btn--sm">删除</button>
<span className="tag tag--running">运行中</span>
```
### 组件文件内部结构
每个组件的 `_index.scss` 按以下顺序组织:
```scss
// 1. 依赖引入
@use '../../tokens' as *;
// 2. Block — 组件根节点
.btn { }
// 3. Elements — 内部元素
.btn__icon { }
// 4. Modifiers — 变体(按语义分组)
// 颜色变体
.btn--primary { }
.btn--danger { }
// 尺寸变体
.btn--sm { }
.btn--lg { }
// 状态变体
.btn--loading { }
// 5. Legacy 兼容别名(过渡期)
.text-btn { @extend .btn--ghost; }
```
### 新增组件开发流程
1. **确认组件层级**:属于 tokens/core/layouts/components/pages 哪一层
2. **创建目录**`src/styles/components/{name}/`
3. **创建 `_index.scss`**:按 BEM 结构编写样式
4. **引入 tokens**`@use '../../tokens' as *;` 使用设计令牌
5. **在 `_index.scss` 中注册**`components/_index.scss` 添加 `@forward '{name}';`
6. **在 JSX 中使用**`className="block__element--modifier"`
7. **验证构建**`pnpm build`
### 新增页面样式开发流程
1. **优先复用组件库**:先检查 `components/` 是否已有可用组件
2. **页面特有组件**:在 `pages/_{page}.scss` 中定义
3. **仅放页面独有的样式**:可复用的应提取到 `components/`
4. **不硬编码值**:使用 tokens 中的变量或 CSS 变量
### 内联样式规则
**禁止使用内联 style 的场景:**
- 可复用的组件样式(按钮、卡片、表格操作列等)
- 使用 tokens 中已有变量的值
- 多个页面/组件中重复出现的模式
**允许使用内联 style 的场景:**
- 各表特有的内容列宽(如 `style={{ width: '180px' }}`
- 动态计算值(如进度条宽度 `width: 65%`
- 聊天消息内容中的排版(通过 `dangerouslySetInnerHTML` 渲染)
### 表格操作列规范
表格操作列统一使用以下类名,禁止写内联样式:
```jsx
// 表头 — 操作列宽度
<th className="col-actions">操作</th> // 200px
<th className="col-actions--narrow">操作</th> // 120px
<th className="col-actions--tiny">操作</th> // 80px
// 单元格 — 操作按钮容器
<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>
// 可点击行
<tr className="tr-clickable" onClick={...}>
```
### 按钮使用规范
| 场景 | 类名 | 颜色 |
|------|------|------|
| 主操作(确认、提交) | `btn btn--primary` | 蓝色实心 |
| 次要操作(取消、重置) | `btn` | 灰色边框 |
| 表格内编辑 | `text-btn text-btn-primary` | 蓝色文字 |
| 表格内删除/禁用 | `text-btn text-btn-danger` | 红色文字 |
| 危险操作确认 | `btn btn--danger` | 红色实心 |
| 警告操作 | `btn btn--warning` | 橙色实心 |
### 状态标签规范
```jsx
// 运行状态
<span className="tag tag--running">运行中</span>
<span className="tag tag--stopped">已停止</span>
<span className="tag tag--error">失败</span>
<span className="tag tag--warning">警告</span>
// 用户角色
<span className="tag tag--admin">管理员</span>
<span className="tag tag--member">成员</span>
<span className="tag tag--developer">开发者</span>
```
### 引用 Tokens 的方式
```scss
// 在组件或页面文件中
@use '../../tokens' as *;
// 直接使用 SCSS 变量
.my-class {
color: $primary;
padding: $spacing-4;
border-radius: $radius-md;
font-weight: $font-weight-semibold;
box-shadow: $shadow-card;
}
// 使用 CSS 变量(适合需要运行时切换的场景)
.my-class {
color: var(--color-primary);
background: var(--color-bg-1);
}
```
### 样式文件引用规则
```scss
// tokens 层:无依赖
@use 'colors' as *;
// core 层:依赖 tokens
@use '../tokens' as *;
// layouts 层:依赖 tokens
@use '../tokens' as *;
// components 层:依赖 tokens
@use '../../tokens' as *;
// pages 层:依赖 tokens
@use '../tokens' as *;
```
**禁止**:跨层引用(如 components 直接引用 pages
### 响应式开发
使用 tokens 中定义的断点,通过 mixins 调用:
```scss
@use '../../tokens' as *;
@include mobile {
// <= 768px
.my-component {
flex-direction: column;
}
}
@include tablet {
// 769px ~ 1024px
}
@include desktop {
// >= 1025px
}
```
## 数据访问层
项目使用统一的数据访问接口 `src/services/api.js`,所有数据获取都通过 API 层进行,便于未来对接后端服务。
### API 使用示例
```javascript
import { api } from '../services/api.js';
// 获取技能列表
const skills = api.skills.list();
// 获取单个技能详情
const skill = api.skills.getById('1');
// 获取日志列表
const logs = api.logs.list();
// 按条件筛选日志
const filteredLogs = api.logs.filter({ user, type, status });
// 获取开发者技能
const mySkills = api.developer.getMySkills();
// 获取成员列表
const members = api.members.list();
```
### API 模块结构
- `api.user` - 用户信息
- `api.skills` - 技能市场(列表、详情、文件、版本、图标)
- `api.conversations` - 聊天场景和对话历史
- `api.logs` - 操作日志(列表、筛选)
- `api.developer` - 开发台数据(总览、技能、分类、文档)
- `api.members` - 项目成员
- `api.tasks` - 定时任务
- `api.admin` - 管理台(总览、部门、用户、项目、模型配置、全局日志)
## 数据模拟
所有数据都存储在 `src/data/` 目录下的JavaScript文件中作为静态模拟数据。API 服务层统一从这些文件读取数据。
### 数据文件说明
- `conversations.js`:聊天场景和对话历史
- `skills.js`技能市场数据包含技能详情含状态dev/published/unlisting/unlisted、文件列表、版本历史含状态reviewing/approved/rejected/withdrawn、拒绝理由、审核列表pendingVersionReviews、pendingUnlistReviews
- `developerData.js`开发台数据包含我的技能含图标、版本审核状态、hasPendingReview标识、技能分类、开发者总览、开发文档
- `logs.js`:操作日志数据(成功/失败/警告状态)
- `tasks.js`:定时任务数据(包含任务配置和执行日志)
- `adminData.js`:管理台数据(部门列表、用户列表、项目列表、模型配置列表、总览指标、全局日志、可选项数据)
- `configTypes.js`模型配置类型注册表OpenAI兼容接口、智算管理平台等类型定义
- `members.js`:项目成员数据
## 构建和部署
### 构建配置vite.config.js
```javascript
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
import { viteSingleFile } from 'vite-plugin-singlefile';
export default defineConfig({
base: './', // 相对路径支持直接打开HTML文件
plugins: [
react(),
viteSingleFile() // 单文件打包
],
});
```
### 构建产物
运行 `pnpm build` 后生成:
- `dist/index.html`包含所有JavaScript和CSS的单个HTML文件
- 支持直接双击打开,无需服务器
- 使用HashRouter路由正常工作
### 部署方式
1. **本地运行**:直接双击 `dist/index.html`
2. **静态服务器**:将 `dist/` 目录部署到任何静态文件服务器
3. **CDN部署**上传单个HTML文件到CDN即可
## 已知问题和注意事项
### 1. 聊天场景渲染
- 聊天内容使用 `dangerouslySetInnerHTML` 渲染HTML字符串
- 深度思考区域支持点击展开/折叠
- 聊天输入框仅为展示,无实际功能
### 2. 浏览器兼容性
- 需要现代浏览器Chrome、Firefox、Safari、Edge
- 不支持IE
- file://协议下可能存在某些限制
### 3. 状态持久化
- 使用localStorage存储子页面状态
- 清除浏览器数据会丢失导航状态
- 不同浏览器标签页共享localStorage
### 4. 性能考虑
- 单个HTML文件较大首次加载可能稍慢
- 所有图标已通过react-icons优化按需加载
- SCSS样式已编译为CSS无运行时开销
### 5. 构建警告
- 构建时会显示弃用警告:`WARN inlineDynamicImports option is deprecated, please use codeSplitting: false instead.`
- 这个警告来自 `vite-plugin-singlefile` 插件内部,无法通过配置消除
- 警告不影响功能,可以安全忽略
## 开发建议
### 添加新页面
1.`src/pages/` 目录下创建页面组件
2. 在父页面组件(如 ConsolePage、AdminPage中添加路由逻辑
3. 如果需要持久化状态,添加 localStorage 逻辑
4. 页面特有样式添加到 `src/styles/pages/_{module}.scss`(优先复用组件库)
### 添加新组件
1.`src/components/` 目录下创建组件
2.`src/styles/components/{name}/` 下创建 `_index.scss`
3. 使用 `@use '../../tokens' as *;` 引入设计令牌
4. 使用 BEM 命名:`.block__element--modifier`
5.`src/styles/components/_index.scss` 中添加 `@forward '{name}';`
### 修改样式
1. **全局调整**:修改 `src/styles/tokens/` 中的令牌变量
2. **组件调整**:修改 `src/styles/components/{name}/_index.scss`
3. **页面调整**:修改 `src/styles/pages/_{module}.scss`
4. 禁止直接修改 `global.scss`(它是纯入口文件)
### 样式验证清单
- [ ] 类名是否遵循 BEM 规范
- [ ] 颜色/间距/字号是否使用 tokens 变量
- [ ] 是否存在重复定义的样式
- [ ] 可复用样式是否放在 `components/` 而非 `pages/`
- [ ] 表格操作列是否使用 `.col-actions` + `.table-actions`
- [ ] `pnpm build` 是否通过
---
## 更多文档
审核审批流程的详细说明请查看:[docs/审核流程.md](docs/审核流程.md)
*最后更新2026-03-26*