山东大学软件学院创新实训——个人纪录(七)
一、概览
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 承担三个职责:
- CSS 重置:统一的
box-sizing、字体平滑、滚动行为 - 全局动画关键帧:定义了
fadeIn、slideUp、pulse、bounce、scaleIn、shake、shimmer、gradientShift共八个通用动画 - 动画工具类:提供
.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 扩展性
如果要新增"老年人"主题,只需三步:
- 在
themes.scss顶部定义$senior-*变量组 - 添加
.theme-senior { @include theme-variables(...) } - 在
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。
检查一下为什么单词卡片样式变样了。可能的原因包括包裹组件添加了额外动画、组件库默认样式冲突、各页面有覆盖样式等,帮我逐一排查并修复。后续调整卡片宽度和进度条宽度,让它们保持一致。
更多推荐


所有评论(0)