一、概览

LingualSpark 是一个面向全年龄段用户的英语学习平台,需要同时服务四个差异巨大的用户群体——幼儿小学生中学生成人。这意味着项目不能只有单一的视觉风格:

  • 幼儿需要大字体、鲜艳色彩和弹性动画来吸引注意力
  • 成人则需要简洁克制的设计来提高阅读效率
  • 不同年龄段对字号、圆角、间距、交互反馈的感知差异显著

因此,一套灵活、可扩展、支持多主题动态切换的样式管理架构,成为了项目的前端核心基建之一。


二、整体架构:四层样式体系

项目的样式管理采用分层架构,从上到下共有四层:

┌─────────────────────────────────────────┐
│  第四层:页面 / 组件样式                  │
│  pages/*.scss    components/*.scss      │  ← 独立维护,引用底层变量
├─────────────────────────────────────────┤
│  第三层:Ant Design 主题适配             │
│  ConfigProvider   theme.token           │  ← 对齐第三方组件库
├─────────────────────────────────────────┤
│  第二层:主题切换引擎                     │
│  themes.scss   +   ThemeManager.tsx     │  ← 四套设计语言的注入与切换
├─────────────────────────────────────────┤
│  第一层:全局基础样式                     │
│  variables.scss   +   base.scss          │  ← 设计令牌 + CSS 重置 + 动画
└─────────────────────────────────────────┘

2.1 样式入口:index.scss

所有全局样式通过一个统一入口按顺序导入,确保变量定义最先加载:

// index.scss —— 全局样式统一入口
@use './styles/variables.scss';   // 第一层:CSS 变量定义
@use './styles/base.scss';        // 第一层:基础重置 + 动画
@use './styles/components.scss';  // 第三层:组件库样式说明
@use './styles/app.scss';         // 第四层:App 布局样式

@use 是 SCSS 的模块化导入语法,比传统的 @import 更严格——它能避免变量污染,并且在编译时就检查引用是否正确。

2.2 文件职责一览

文件 层级 职责
variables.scss 第一层 定义全部 CSS 自定义属性(颜色、阴影、圆角、间距、字体、动画、布局、层级)
base.scss 第一层 CSS 基础重置、body/root 基础样式、全局动画关键帧、动画工具类
themes.scss 第二层 四套年龄主题的 SCSS 变量 + mixin 注入 + 主题类名定义
ThemeManager.tsx 第二层 React 组件,监听用户年龄组并动态切换 #root 上的主题类名
main.tsx 第三层 ConfigProvider 配置,将 Ant Design 设计令牌对齐到项目变量
app.scss 第四层 App 根容器 + Dashboard 首页布局 + 响应式 + 主题适配
pages/*.scss 第四层 各页面的独立样式(引用 variables.scss 中的 CSS 变量)
components/*.scss 第四层 各组件的独立样式(如 WordCard、Navbar、Footer 等)

三、第一层:设计令牌系统

3.1 variables.scss —— 一切的基础

variables.scss 将全部设计参数收敛到 CSS 自定义属性中,遵循 --{类别}-{层级} 的命名规范。整个文件超过 300 行,分成十个区块:

区块 变量示例 说明
颜色系统 --primary-color: #667eea--success-color: #10b981 主色、辅助色、状态色、四级背景色、四级文字色
阴影系统 --shadow-sm--shadow-2xl--shadow-card--shadow-hover 从微弱投影到强烈悬浮的六级阴影
圆角系统 --radius-sm (6px) → --radius-full (9999px) 不同年龄段会映射到不同的具体数值
间距系统 --spacing-xs--spacing-4xl 八级间距,语义化命名
字体系统 --text-xs--text-6xl--font-normal--font-extrabold 字号 + 字重
过渡动画 --transition-fast (150ms) → --transition-slowest (500ms) 统一缓动函数 cubic-bezier(0.4, 0, 0.2, 1)
布局系统 --container-max-width: 1200px--breakpoint-sm--breakpoint-2xl 容器宽度 + 响应式断点
层级系统 --z-base (1) → --z-loading (500) 覆盖层级管理
兼容性别名 --card-background: var(--bg-card) 旧代码迁移过渡
主题专属变量 --toddler-*--pupil-* 各年龄段的 SCSS 变量(供 themes.scss 使用)

3.2 核心设计思想:间接引用

部分组件代码中永远不编码具体数值,而是引用语义化变量。例如:

border-radius: var(--card-radius);
box-shadow: var(--shadow-card);
color: var(--text-secondary);
transition: var(--transition);

这样做的好处是:当用户切换到幼儿主题时,--card-radius 会被自动替换为 32px,--transition 会变成弹性缓动——组件代码一行不改,视觉效果完全切换

3.3 base.scss —— 基础重置与动画库

base.scss 承担三个职责:

  1. CSS 重置:统一的 box-sizing、字体平滑、滚动行为
  2. 全局动画关键帧:定义了 fadeInslideUppulsebouncescaleInshakeshimmergradientShift 共八个通用动画
  3. 动画工具类:提供 .animate-fade-in.animate-slide-up 等可直接使用的 CSS 类

此外还定义了一个 .card 基础类,作为全局卡片的默认样式模板。


四、第二层:年龄分层主题系统

4.1 四套设计语言的参数差异

这是整个架构中最核心的设计。项目定义了四套主题参数,每一套都包含多个独立变量:

参数 幼儿 小学 中学 成人
主色调 浅粉 #FFB6C1 亮蓝 #3B82F6 深蓝 #1E40AF 深灰 #1F2937
卡片圆角 32px 28px 14px 6px
按钮圆角 28px 24px 10px 6px
hover 缩放 1.08 1.04 1.02 1.005
过渡动画 弹性 400ms 弹性 300ms 标准 250ms 快速 150ms
基础字号 18px 16px 14px 13px
导航栏高度 88px 76px 66px 52px
按钮内边距 18px 36px 14px 30px 10px 22px 8px 16px

幼儿主题的核心特征是"大、圆、弹"——大字号适合低龄用户的视觉能力,大圆角带来亲和感,弹性缓动 cubic-bezier(0.34, 1.56, 0.64, 1) 让交互像弹簧一样有趣。

成人主题则完全相反——小字号、接近直角的圆角、几乎不缩放的 hover、最短的过渡时间——一切设计都为效率和克制服务。

4.2 SCSS Mixin:注入设计令牌

themes.scss 定义了一个核心 mixin,接收 22 个参数并将其注入为 CSS 自定义属性:

@mixin theme-variables(
  $primary, $secondary, $accent,
  $text-primary, $text-secondary,
  $background, $border,
  $card-radius, $card-shadow,
  $button-radius, $button-font-size,
  $hover-scale, $transition,
  $font-size-base, $spacing,
  $nav-height, $button-padding,
  $word-image-size,
  $story-font-size, $story-line-height,
  $button-min-width
) {
  --primary-color: #{$primary};
  --secondary-color: #{$secondary};
  --card-radius: #{$card-radius};
  --hover-scale: #{$hover-scale};
  --transition: #{$transition};
  // ... 共 22 个变量映射
}

四套主题通过一行代码完成注入:

.theme-toddler {
  @include theme-variables(
    $toddler-primary, $toddler-secondary, /* ... 共 22 个参数 */
  );
}
.theme-pupil { @include theme-variables($pupil-primary, /* ... */); }
.theme-middle { @include theme-variables($middle-primary, /* ... */); }
.theme-adult { @include theme-variables($adult-primary, /* ... */); }

4.3 扩展性

如果要新增"老年人"主题,只需三步:

  1. themes.scss 顶部定义 $senior-* 变量组
  2. 添加 .theme-senior { @include theme-variables(...) }
  3. ThemeManager.tsx 中加一个 case 分支

所有页面的样式自动适配,零行组件代码修改。


五、运行时主题切换:ThemeManager

ThemeManager.tsx 是一个纯逻辑 React 组件,不渲染任何 DOM,只做一件事——把用户的年龄组映射为主题类名,挂载到 #root 元素上

const ThemeManager: React.FC<{ children: ReactNode }> = ({ children }) => {
  const { user } = useAuthStore();

  // age_group → 主题类名映射
  const getThemeClass = () => {
    switch (user?.age_group) {
      case 1: return 'theme-toddler';   // 幼儿
      case 2: return 'theme-pupil';     // 小学
      case 3: return 'theme-middle';    // 中学
      case 4: return 'theme-adult';     // 成人
      default: return 'theme-adult';
    }
  };

  // 监听到年龄组变化时,替换 #root 上的 class
  useEffect(() => {
    const root = document.getElementById('root');
    if (root) {
      root.classList.remove('theme-toddler', 'theme-pupil',
                             'theme-middle', 'theme-adult');
      root.classList.add(getThemeClass());
    }
  }, [user?.age_group]);

  return <>{children}</>;
};

选择在 #root 上挂载主题类名,是因为后代选择器 .theme-toddler .word-card 可以穿透任何层级的组件,而不产生全局污染。上层主题类名切换后,所有后代元素的 var(--card-radius) 等变量会立即解析为新值,页面在用户无感知的情况下完成视觉切换。


六、第三层:Ant Design 深度集成

项目大量使用 Ant Design 组件库,但它的默认蓝色主题与 LingualSpark 的蓝紫渐变风格不匹配。解决方式是——在 main.tsx 中通过 ConfigProvider 把 Ant Design 的 20+ 个设计令牌精确对齐到项目变量

const themeConfig = {
  token: {
    colorPrimary: '#667eea',        // → var(--primary-color)
    colorSuccess: '#10b981',        // → var(--success-color)
    colorError: '#ef4444',          // → var(--error-color)
    borderRadius: 8,               // → var(--radius-md)
    fontSize: 14,                  // → var(--text-base)
    colorText: '#1e293b',          // → var(--text-primary)
    colorBgContainer: '#f8fafc',   // → var(--bg-secondary)
    // ... 共 20+ 项映射
  },
  components: {
    Button: { borderRadius: 12, height: 40 },
    Card: { borderRadius: 16 },
    Progress: { borderRadius: 8 },
    // ...
  },
};

这样做的好处:

  • Button、Card、Progress、Modal 等所有 Ant Design 组件的默认样式与项目设计规范天然对齐
  • 不需要在每个使用处单独覆写样式
  • 原先 components.scss 中大量针对 Ant Design 的样式覆盖代码得以删除

七、第四层:页面与组件的样式规范

7.1 文件组织

每个页面和组件独立维护同名 .scss 文件,放在自身目录下:

src/
├── pages/
│   ├── DailyWordMemoryPage.tsx
│   ├── DailyWordMemoryPage.scss    ← 同名样式文件
│   ├── WordBookReviewPage.tsx
│   └── WordBookReviewPage.scss
├── components/
│   ├── WordCard.tsx
│   ├── WordCard.scss
│   ├── Navbar.tsx
│   └── Navbar.scss

7.2 样式编写约定

引用全局变量,不硬编码数值

// 每个 SCSS 文件顶部统一引用
@use '../styles/variables.scss';

.page {
  color: var(--text-primary);       // 而非 #1e293b
  border-radius: var(--card-radius); // 而非 12px
  transition: var(--transition);     // 而非 0.3s ease
}

SCSS 嵌套保持层次清晰

所有子元素、伪类、伪元素都嵌套在父选择器内,利用 & 引用父选择器:

.card-section {
  display: flex;
  gap: 1.5rem;

  .card-section__side {          // 子元素
    flex: 0 0 220px;
    position: relative;
  }

  .card-section__side-orb {      // BEM 子元素
    position: absolute;

    &--left {                    // BEM 修饰符
      background: radial-gradient(...);
      animation: floatOrb 8s ease-in-out infinite;
    }
    &--right {
      background: radial-gradient(...);
      animation: floatOrb 10s ease-in-out infinite reverse;
    }
  }
}

八、架构总览图

用户登录 → age_group 字段
    │
    ▼
ThemeManager.tsx
    │  读取 age_group
    │  操作 #root.classList
    ▼
┌──────────────────────────────────────────┐
│  #root (class="theme-toddler")           │
│                                          │
│  所有 CSS 变量自动替换为幼儿主题值: 	       │
│  --primary-color  = #FFB6C1              │
│  --card-radius    = 32px                 │
│  --hover-scale    = 1.08                 │
│  --transition     = 0.4s cubic-bezier()  │
└──────────────────────────────────────────┘
    │
    ├──▶ Ant Design 组件
    │    ConfigProvider 独立配置,不受主题切换影响
    │
    └──▶ 自定义组件 / 页面
         通过 var(--xxx) 自动适配当前主题

附.开发使用AI生成代码的提示词过程纪录

帮我在 variables.scss 中建立一套 CSS 自定义属性体系,用 :root 包裹。需要覆盖颜色、阴影、圆角、间距、字体、过渡动画、布局、层级等维度的设计令牌。命名规范统一为 --{类别}-{层级},每个区块用注释分隔。同时预留兼容性别名,方便旧代码迁移。后续迭代中再补充响应式字体覆盖和深色模式适配。

帮我设计四套年龄分层主题变量,分别为幼儿、小学、中学、成人,每套覆盖颜色、圆角、阴影、字号、间距、过渡动画、hover 缩放等参数。幼儿走亲和圆润的马卡龙风格,成人走克制极简的高级风,小学和中学介于两者之间。定义一个 SCSS mixin 接收这些参数并注入为 CSS 自定义属性,然后为四个主题分别创建对应的类名调用 mixin。每个主题类名内额外添加一些该年龄段的专属特殊变量。最后在 :root 上以成人主题为默认值做兜底。

创建一个 React 组件(ThemeManager),根据用户登录后返回的年龄组字段,动态切换 #root 上的主题类名。从 Zustand store 中读取用户信息,用 useEffect 监听年龄组变化,先移除所有旧主题类名再添加新类名。组件本身不渲染任何 DOM,只透传 children。

main.tsx 中通过 ConfigProvider 把 Ant Design 的设计令牌(颜色、圆角、字号、阴影、间距等)全部映射到项目自己的设计规范值,使组件库默认样式与项目风格天然对齐。ConfigProvider 包裹在 ThemeManager 外层,使用中文 locale。

检查一下为什么单词卡片样式变样了。可能的原因包括包裹组件添加了额外动画、组件库默认样式冲突、各页面有覆盖样式等,帮我逐一排查并修复。后续调整卡片宽度和进度条宽度,让它们保持一致。

Logo

一站式 AI 云服务平台

更多推荐