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

34 KiB
Raw Blame History

GrandClaw 原型项目

项目概述

GrandClaw 是一个企业级AI智能助手平台的前端原型项目主要用于展示平台的主要页面布局、交互流程和视觉设计。项目采用现代化的前端技术栈实现了四大核心模块

  • 首页:平台入口,包含登录功能
  • 工作台用户与AI助手交互的主要界面包含聊天、技能市场、日志查询、定时任务等功能
  • 管理台:运营管理界面,包含总览、部门管理、用户管理、项目管理
  • 开发台:技能开发界面,包含我的技能、技能编辑、开发文档等

技术栈

核心框架

  • React 19.2.4UI组件库
  • React Router 7.13.1路由管理使用HashRouter
  • Vite 8.0.1:构建工具和开发服务器

UI与样式

  • react-icons 5.5.0图标库Feather + FontAwesome图标集
  • Sass 1.98.0CSS预处理器
  • 五层分层架构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

安装依赖

pnpm install

开发模式

pnpm dev
# 访问 http://localhost:5173

构建生产版本

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

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 品牌区域

侧边栏顶部的品牌展示组件。

import { SidebarBrand } from '../components/layout/SidebarBrand.jsx';

<SidebarBrand subtitle="企业级AI平台" />

SidebarUser 用户信息

侧边栏底部的用户信息展示组件,使用全局用户上下文。

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 导航项

侧边栏导航菜单项组件。

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 空状态

用于展示空列表或无数据状态的组件。

import EmptyState from '../components/common/EmptyState.jsx';

<EmptyState
  icon={<FiInbox size={48} />}
  title="暂无数据"
  description="当前没有可显示的内容"
/>

Modal 弹窗

用于展示确认操作的弹窗组件,支持自定义标题和内容。

import Modal from '../components/common/Modal.jsx';

<Modal
  visible={showModal}
  title="确认删除"
  onConfirm={handleConfirm}
  onCancel={handleCancel}
  confirmText="删除"
>
  确定要删除这个任务吗
</Modal>

Toast 消息提示

用于展示操作结果的消息提示组件,支持成功、错误、警告、信息四种类型。

import Toast from '../components/common/Toast.jsx';

<Toast
  visible={showToast}
  type="success"
  message="操作成功"
  onClose={() => setShowToast(false)}
/>

支持的类型:

  • success - 成功(绿色)
  • error - 错误(红色)
  • warning - 警告(黄色)
  • info - 信息(蓝色)

StatusBadge 状态标签

用于显示状态(成功、失败、警告等)的标签组件。

import StatusBadge from '../components/common/StatusBadge.jsx';

<StatusBadge status="success" text="运行中" />
<StatusBadge status="error" text="失败" />
<StatusBadge status="warning" text="警告" />
<StatusBadge status="stopped" text="已停止" />

TagInput 标签输入

支持输入标签的输入框组件。

import TagInput from '../components/common/TagInput.jsx';

<TagInput
  tags={tags}
  onChange={setTags}
  placeholder="输入标签后按回车添加"
/>

SearchBar 搜索框

通用的搜索输入框组件。

import SearchBar from '../components/common/SearchBar.jsx';

<SearchBar
  value={searchQuery}
  onChange={setSearchQuery}
  placeholder="搜索..."
/>

列表组件

ListSelector 列表选择器

通用的列表选择器组件,支持单选和多选模式。

import ListSelector from '../components/ListSelector.jsx';

<ListSelector
  data={dataList}              // 数据数组
  selectedIds={selectedIds}     // 已选项
  onChange={handleChange}       // 选择变化回调
  searchPlaceholder="搜索..."   // 搜索框占位符
  columns={columns}             // 列配置 [{ key, label }]
  multiSelect={false}          // 是否多选
  selectedLabel="已选标签"       // 已选标签文字
  onClearSelected={() => {}}   // 清除已选回调
/>

状态管理

全局状态

UserContext 用户信息上下文

全局用户信息状态,通过 UserProvider 提供给整个应用。

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 持久化和主页跳转重置。

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支持携带额外数据。

import { useNavigation } from '../hooks/useNavigation.js';

const { navigateToPage, extraData } = useNavigation(setCurrentPage);

// 导航到指定页面
navigateToPage('skills', { skillId: '1' });

// 获取导航传递的数据
const skillId = extraData.skillId;

useLocalStorage localStorage 状态管理

同步组件状态到 localStorage 的 Hook。

import { useLocalStorage } from '../hooks/useLocalStorage.js';

const [value, setValue] = useLocalStorage('myKey', 'defaultValue');
setValue('newValue'); // 自动同步到 localStorage

导航状态持久化策略

每个主要页面(工作台、管理台、开发台)都有独立的localStorage键:

// 工作台
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区分两种导航来源:

// 从主页跳转:显示默认页面
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设计令牌

所有设计决策集中定义,禁止在组件或页面中硬编码值。

// 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 引入,直接使用变量名。

@use '../../tokens' as *;

.my-component {
    color: $text-1;
    padding: $spacing-4;
    font-weight: $font-weight-semibold;
}

同时提供 CSS 变量core 层定义),供运行时和 JSX 内联场景使用:

// 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 语句,无任何直接样式定义:

// 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

// Block — 组件根节点
.card { }

// Element — 组件内部元素,用双下划线连接
.card__header { }
.card__body { }
.card__footer { }

// Modifier — 变体/状态,用双连字符连接
.card--flat { }
.card--elevated { }
.btn--primary { }
.btn--danger { }
.tag--running { }
.tag--admin { }

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 按以下顺序组织:

// 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 渲染)

表格操作列规范

表格操作列统一使用以下类名,禁止写内联样式:

// 表头 — 操作列宽度
<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 橙色实心

状态标签规范

// 运行状态
<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 的方式

// 在组件或页面文件中
@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);
}

样式文件引用规则

// 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 调用:

@use '../../tokens' as *;

@include mobile {
    // <= 768px
    .my-component {
        flex-direction: column;
    }
}

@include tablet {
    // 769px ~ 1024px
}

@include desktop {
    // >= 1025px
}

数据访问层

项目使用统一的数据访问接口 src/services/api.js,所有数据获取都通过 API 层进行,便于未来对接后端服务。

API 使用示例

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

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

最后更新2026-03-26