```
### 第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
运行中
```
### 组件文件内部结构
每个组件的 `_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
// 表头 — 操作列宽度
操作 | // 200px
操作 | // 120px
操作 | // 80px
// 单元格 — 操作按钮容器
|
// 可点击行
```
### 按钮使用规范
| 场景 | 类名 | 颜色 |
|------|------|------|
| 主操作(确认、提交) | `btn btn--primary` | 蓝色实心 |
| 次要操作(取消、重置) | `btn` | 灰色边框 |
| 表格内编辑 | `text-btn text-btn-primary` | 蓝色文字 |
| 表格内删除/禁用 | `text-btn text-btn-danger` | 红色文字 |
| 危险操作确认 | `btn btn--danger` | 红色实心 |
| 警告操作 | `btn btn--warning` | 橙色实心 |
### 状态标签规范
```jsx
// 运行状态
运行中
已停止
失败
警告
// 用户角色
管理员
成员
开发者
```
### 引用 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*