1. SCSS高级语法
🌟**【青柠代码录】--- 青柠来相伴,代码更简单** 🌟
🔥**【全栈】博客合集:** https://www.yuque.com/u12587869/zplytb/ur5ohwqxd2axtiny 🔥
🎯**【Java】面试题:** https://www.yuque.com/u12587869/zplytb/eh7yqzitiab693og 🎯
1.1 条件判断(@if/@else):多场景样式适配
@if/@else 可根据传入参数、变量值生成不同样式,核心用于「多状态、多配置」的组件开发(如按钮尺寸、卡片阴影深度、状态切换样式),搭配参数校验,能有效避免开发中的非法参数错误,提升代码健壮性。
实战场景:通用按钮(多尺寸、多状态),结合上一篇的基础混合器,完善动态适配逻辑,支持small/base/large三种尺寸,以及默认/禁用/高亮三种状态。
// 全局混合器:src/styles/_mixins.scss(承接第一篇,补充完善)
// 先引入全局变量(确保样式统一)
@import "@/styles/_variables.scss";
// 1. 按钮尺寸混合器(带参数校验)
@mixin btn-size($size: base) {
// 校验参数合法性:仅支持small/base/large,非法参数抛出错误(便于调试)
@if not index(('small', 'base', 'large'), $size) {
@error "按钮尺寸仅支持 small/base/large,当前传入:#{$size}";
}
// 小尺寸按钮(适配列表操作、弹窗内按钮)
@if $size == small {
height: 32px;
padding: 4px 12px;
font-size: 12px;
border-radius: $border-radius-sm; // 全局小圆角变量
}
// 基础尺寸按钮(默认,适配大部分场景)
@else if $size == base {
height: 40px;
padding: 8px 16px;
font-size: $font-size-base; // 全局基础字体变量
border-radius: $border-radius-base; // 全局基础圆角变量
}
// 大尺寸按钮(适配表单提交、首页核心操作)
@else if $size == large {
height: 48px;
padding: 12px 24px;
font-size: 16px;
border-radius: $border-radius-lg; // 全局大圆角变量
}
}
// 2. 按钮状态混合器(结合条件判断)
@mixin btn-status($status: default) {
@if $status == default {
background-color: $primary-color; // 全局主色调
color: #fff;
border: 1px solid transparent;
&:hover {
background-color: darken($primary-color, 8%); // 内置函数:颜色加深
}
} @else if $status == disabled {
background-color: $border-color; // 全局边框色
color: #999;
border: 1px solid $border-color;
cursor: not-allowed;
&:hover {
background-color: $border-color; // 禁用状态取消hover效果
}
} @else if $status == highlight {
background-color: #fff;
color: $primary-color;
border: 1px solid $primary-color;
&:hover {
background-color: lighten($primary-color, 15%); // 内置函数:颜色变浅
}
}
}
// 3. 整合按钮混合器(简化组件使用)
@mixin btn-common($size: base, $status: default) {
@include btn-size($size);
@include btn-status($status);
outline: none;
cursor: pointer;
transition: background-color 0.2s ease, transform 0.1s ease;
&:active {
transform: scale(0.98); // 点击反馈效果
}
}
组件中使用示例(Vue3单文件组件,承接第一篇的通用按钮组件,补充动态配置):
<template>
<button
class="btn"
:class="{ 'btn--disabled': disabled }"
:disabled="disabled"
@click="handleClick"
>
<slot>按钮</slot>
</button>
</template>
<script setup lang="ts">
import { defineProps, emit } from 'vue';
// 组件props(支持尺寸、状态自定义,与SCSS混合器参数对应)
const props = defineProps({
size: {
type: String,
default: 'base',
validator: (value: string) => ['small', 'base', 'large'].includes(value)
},
status: {
type: String,
default: 'default',
validator: (value: string) => ['default', 'disabled', 'highlight'].includes(value)
},
disabled: {
type: Boolean,
default: false
}
});
const emit = emit('click');
const handleClick = () => {
if (!props.disabled) {
emit('click');
}
};
</script>
<style lang="scss">
// 直接使用全局混合器,传入props参数,实现动态样式
.btn {
@include btn-common($size: #{props.size}, $status: #{props.status});
// 补充额外样式,适配组件特殊需求
&--disabled {
opacity: 0.7;
}
}
</style>
注意事项:
-
使用
@if/@else时,建议添加参数校验(@error),避免传入非法参数导致样式异常,尤其适合多团队协作场景; -
条件判断中可结合SCSS内置函数(如
darken/lighten),实现更灵活的样式动态调整; -
Vue3组件中使用时,通过
#{props.xxx}将props值传入SCSS,实现组件props与SCSS样式的联动。
1.2 循环语句(@for/@each):批量生成样式
项目中,经常会遇到「重复样式批量生成」的场景(如间距类、主题色类、响应式断点类),使用@for(数值循环)和@each(列表循环),可大幅减少冗余代码,提升开发效率,同时保证样式统一性。
1.2.1 @for循环:数值循环(适合连续数值场景)
实战场景 :批量生成全局间距类(通用规范),适配不同场景的内边距/外边距,避免重复书写.spacing-1、.spacing-2等类名。
// src/styles/_mixins.scss(新增间距循环)
// 批量生成间距类(内边距)
@for $i from 1 through 8 {
// 定义步长:每级间距增加4px(规范,可自定义)
$spacing-value: $i * 4px;
// 内边距类:p-1 ~ p-8
.p-#{$i} {
padding: $spacing-value;
}
// 上下内边距类:py-1 ~ py-8
.py-#{$i} {
padding-top: $spacing-value;
padding-bottom: $spacing-value;
}
// 左右内边距类:px-1 ~ px-8
.px-#{$i} {
padding-left: $spacing-value;
padding-right: $spacing-value;
}
}
// 批量生成间距类(外边距,与内边距对应)
@for $i from 1 through 8 {
$spacing-value: $i * 4px;
.m-#{$i} {
margin: $spacing-value;
}
.my-#{$i} {
margin-top: $spacing-value;
margin-bottom: $spacing-value;
}
.mx-#{$i} {
margin-left: $spacing-value;
margin-right: $spacing-value;
}
}
编译后效果(自动生成32个间距类,无需手动书写):
.p-1 { padding: 4px; }
.py-1 { padding-top: 4px; padding-bottom: 4px; }
.px-1 { padding-left: 4px; padding-right: 4px; }
/* ... 依次生成p-2到p-8,m-1到m-8相关类名 */
使用场景:页面布局、组件内间距调整,直接添加类名即可,如<div class="px-4 py-3">内容</div>,无需重复书写padding样式。
1.2.2 @each循环:列表循环(适合非连续、多组数据场景)
实战场景1:批量生成主题色样式(品牌色规范),结合全局主题色列表,批量生成文本色、背景色、边框色类,适配不同模块的主题需求。
// src/styles/_variables.scss(定义主题色列表)
// 品牌色列表(key为主题名,value为色值)
$theme-colors: (
primary: #42b983, // 主色调
secondary: #64748b, // 辅助色
danger: #f56c6c, // 危险色
success: #67c23a, // 成功色
warning: #e6a600, // 警告色
info: #1890ff // 信息色
);
// src/styles/_mixins.scss(批量生成主题色类)
@import "@/styles/_variables.scss";
// 批量生成主题色文本类(text-primary、text-danger等)
@each $name, $color in $theme-colors {
.text-#{$name} {
color: $color;
// hover效果(统一规范)
&:hover {
color: darken($color, 10%);
transition: color 0.2s ease;
}
}
}
// 批量生成主题色背景类(bg-primary、bg-danger等)
@each $name, $color in $theme-colors {
.bg-#{$name} {
background-color: $color;
color: #fff; // 白色文本,保证可读性
&:hover {
background-color: darken($color, 8%);
transition: background-color 0.2s ease;
}
}
}
// 批量生成主题色边框类(border-primary、border-danger等)
@each $name, $color in $theme-colors {
.border-#{$name} {
border: 1px solid $color;
}
}
实战场景2:批量生成响应式断点类(适配PC/移动端),结合响应式断点列表,批量生成不同屏幕尺寸的适配类,简化响应式开发。
// src/styles/_variables.scss(定义响应式断点)
$breakpoints: (
mobile: 768px, // 移动端(小于768px)
pad: 768px, // 平板(大于等于768px)
pc: 1200px, // 桌面端(大于等于1200px)
large-pc: 1920px // 大屏桌面(大于等于1920px)
);
// src/styles/_mixins.scss(批量生成响应式类)
@each $device, $width in $breakpoints {
@if $device == mobile {
// 移动端:小于指定宽度
@media (max-width: $width) {
.hidden-on-#{$device} { display: none; } // 移动端隐藏
.show-on-#{$device} { display: block; } // 移动端显示
}
} @else {
// 其他设备:大于等于指定宽度
@media (min-width: $width) {
.hidden-on-#{$device} { display: none; } // 对应设备隐藏
.show-on-#{$device} { display: block; } // 对应设备显示
}
}
}
使用示例:
<!-- 移动端显示,PC端隐藏 -->
<div class="show-on-mobile hidden-on-pc">移动端专属内容</div>
<!-- PC端显示,移动端隐藏 -->
<div class="show-on-pc hidden-on-mobile">PC端专属内容</div>
注意事项:
-
@for循环适合「连续数值」场景(如1-8的间距),@each适合「列表数据」场景(如主题色、断点); -
循环中使用
#{$变量名}实现「动态类名/属性值」,注意语法格式,避免遗漏#和{}; -
批量生成的样式需遵循规范,统一步长、 hover效果等,避免样式混乱。
1.3 自定义函数(@function):复杂样式计算
SCSS内置函数(如darken/lighten/hex-to-rgb)可满足基础需求,但项目中经常需要「自定义样式计算逻辑」(如响应式尺寸、颜色转换、动态间距),此时使用@function封装自定义函数,可实现逻辑复用,提升代码可维护性。
自定义函数与混合器(@mixin)的区别:混合器用于「生成样式块」,函数用于「返回计算后的值」,可嵌套在样式属性中使用。
实战场景1:HEX转RGBA(高频需求)
项目中经常需要将十六进制颜色(HEX)转换为带透明度的RGBA颜色,封装自定义函数,避免重复计算,同时保证透明度统一。
// src/styles/_functions.scss(新建自定义函数文件,模块化管理)
// HEX转RGBA函数:参数1=HEX色值,参数2=透明度(默认1,不透明)
@function hex-to-rgba($hex, $alpha: 1) {
// 校验HEX色值合法性(简化版,避免传入非法色值)
@if length($hex) != 7 and length($hex) != 4 {
@error "HEX色值格式错误,需为#fff或#ffffff,当前传入:#{$hex}";
}
// 内置函数hex-to-rgb:将HEX转为RGB数组(如#42b983转为(66, 185, 131))
$rgb: hex-to-rgb($hex);
// 提取RGB各分量
$red: nth($rgb, 1);
$green: nth($rgb, 2);
$blue: nth($rgb, 3);
// 返回RGBA值
@return rgba($red, $green, $blue, $alpha);
}
// 引入到全局混合器,方便使用
// src/styles/_mixins.scss
@import "@/styles/_functions.scss";
使用示例:
.card {
// 主色调,透明度0.8
background-color: hex-to-rgba($primary-color, 0.8);
// 危险色,透明度0.6
border: 1px solid hex-to-rgba($danger-color, 0.6);
}
实战场景2:响应式尺寸计算(适配不同屏幕)
大型项目中,响应式布局需要根据屏幕宽度动态计算元素尺寸(如字体、容器宽度),封装自定义函数,实现「按屏幕比例自适应」,避免写多个媒体查询。
// src/styles/_functions.scss(新增响应式尺寸函数)
// 响应式尺寸计算:参数1=基础尺寸(1920px屏幕下的尺寸),参数2=基准屏幕宽度(默认1920px)
@function responsive-size($base-size, $base-screen: 1920) {
// 校验参数:基础尺寸和基准屏幕宽度必须为正数
@if $base-size <= 0 or $base-screen <= 0 {
@error "基础尺寸和基准屏幕宽度必须为正数,当前传入:base-size=#{$base-size},base-screen=#{$base-screen}";
}
// 计算逻辑:(基础尺寸 / 基准屏幕宽度) * 100vw,实现按屏幕比例自适应
@return ($base-size / $base-screen) * 100vw;
}
// 响应式字体大小(在响应式尺寸基础上,限制最小/最大值,避免字体过小/过大)
@function responsive-font($base-size, $min-size: 12px, $max-size: 24px) {
$responsive: responsive-size($base-size);
// 限制最小值
@if $responsive < $min-size {
@return $min-size;
}
// 限制最大值
@else if $responsive > $max-size {
@return $max-size;
}
// 正常返回响应式尺寸
@else {
@return $responsive;
}
}
使用示例(首页标题响应式适配):
.home-title {
// 1920px屏幕下字体大小为28px,自适应调整,最小16px,最大32px
font-size: responsive-font(28px, 16px, 32px);
// 1920px屏幕下宽度为800px,自适应调整
width: responsive-size(800px);
}
实战场景3:动态行高计算(字体适配)
UI规范中,行高通常与字体大小关联(如行高=字体大小+8px),封装自定义函数,实现行高自动适配,避免手动计算。
// src/styles/_functions.scss(新增行高函数)
@function line-height($font-size, $offset: 8px) {
// 行高=字体大小+偏移量(默认8px,可自定义)
@return $font-size + $offset;
}
// 使用示例
.title {
font-size: 18px;
line-height: line-height(18px); // 行高=18+8=26px
}
.subtitle {
font-size: 14px;
line-height: line-height(14px, 6px); // 自定义偏移量,行高=20px
}
注意事项:
-
自定义函数需添加参数校验(
@error),避免非法参数导致计算错误; -
函数返回值必须是「合法的样式值」(如px、vw、rgba),不能返回样式块;
-
建议将自定义函数单独放在
_functions.scss文件中,模块化管理,便于多团队协作查阅和修改。
2. SCSS主题切换:结合Vue3响应式实现全局亮色/暗色切换
主题切换是项目的高频需求(如后台管理系统、移动端APP),核心需求是「全局样式动态切换」+「用户选择持久化」,结合SCSS变量、CSS变量和Vue3响应式数据,可实现无闪屏、可扩展的主题切换功能。
2.1 核心思路
-
定义多套主题变量(亮色/暗色,可扩展更多主题);
-
将SCSS主题变量映射为CSS全局变量(实现动态切换);
-
通过Vue3响应式数据控制body类名(区分不同主题);
-
使用localStorage持久化用户主题选择(页面刷新后保留);
-
封装主题切换组件,实现无闪屏切换,适配全局使用。
2.2 步骤1:定义主题变量(模块化拆分)
按主题类型拆分SCSS文件,集中管理主题变量,便于后续扩展和维护,目录结构延续第一篇的工程化规范:
src/styles/
├── themes/ // 主题目录(新增)
│ ├── _light.scss // 亮色主题变量
│ ├── _dark.scss // 暗色主题变量
│ └── _theme-map.scss // 主题映射(统一管理所有主题)
├── _variables.scss // 全局公共变量(不随主题变化)
├── _mixins.scss // 全局混合器
├── _functions.scss // 自定义函数
└── main.scss // 样式入口
编写主题变量文件,确保亮色/暗色变量一一对应,便于映射:
// src/styles/themes/_light.scss(亮色主题,默认主题)
// 亮色主题标识(用于区分主题)
$theme-name: 'light' !default;
// 背景色(全局/卡片/表单)
$bg-color: #f8f9fa !default; // 全局背景色
$card-bg-color: #fff !default; // 卡片背景色
$form-bg-color: #fff !default; // 表单背景色
// 文本色(标题/正文/辅助文本)
$text-color: #333 !default; // 正文文本色
$title-color: #222 !default; // 标题文本色
$aux-text-color: #666 !default; // 辅助文本色
// 边框色/分割线色
$border-color: #e5e6eb !default; // 基础边框色
$divider-color: #f0f0f0 !default; // 分割线色
// 主题色(与全局公共变量一致,可单独覆盖)
$primary-color: #42b983 !default; // 主色调
$danger-color: #f56c6c !default; // 危险色
$success-color: #67c23a !default; // 成功色
// 高亮色(hover/选中状态)
$hover-color: lighten($primary-color, 15%) !default; // 主色调hover
$active-color: darken($primary-color, 8%) !default; // 主色调选中
// src/styles/themes/_dark.scss(暗色主题)
$theme-name: 'dark' !default;
// 背景色(深色模式,避免过暗伤眼)
$bg-color: #1e1e1e !default; // 全局背景色
$card-bg-color: #2d2d2d !default; // 卡片背景色
$form-bg-color: #2d2d2d !default; // 表单背景色
// 文本色(深色模式,提高对比度)
$text-color: #fff !default; // 正文文本色
$title-color: #f5f5f5 !default; // 标题文本色
$aux-text-color: #aaa !default; // 辅助文本色
// 边框色/分割线色(深色模式适配)
$border-color: #3d3d3d !default; // 基础边框色
$divider-color: #333 !default; // 分割线色
// 主题色(深色模式下微调,提高对比度)
$primary-color: #5fc39c !default; // 主色调(变亮,适配深色背景)
$danger-color: #f78a8a !default; // 危险色(变亮)
$success-color: #87d068 !default; // 成功色(变亮)
// 高亮色(深色模式适配)
$hover-color: lighten($primary-color, 10%) !default;
$active-color: darken($primary-color, 5%) !default;
编写主题映射文件,统一管理所有主题,便于后续获取主题变量:
// src/styles/themes/_theme-map.scss
// 引入所有主题变量
@use './light' as light;
@use './dark' as dark;
// 主题映射表:key=主题名,value=主题变量集合
$theme-map: (
light: (
bg-color: light.$bg-color,
card-bg-color: light.$card-bg-color,
form-bg-color: light.$form-bg-color,
text-color: light.$text-color,
title-color: light.$title-color,
aux-text-color: light.$aux-text-color,
border-color: light.$border-color,
divider-color: light.$divider-color,
primary-color: light.$primary-color,
danger-color: light.$danger-color,
success-color: light.$success-color,
hover-color: light.$hover-color,
active-color: light.$active-color
),
dark: (
bg-color: dark.$bg-color,
card-bg-color: dark.$card-bg-color,
form-bg-color: dark.$form-bg-color,
text-color: dark.$text-color,
title-color: dark.$title-color,
aux-text-color: dark.$aux-text-color,
border-color: dark.$border-color,
divider-color: dark.$divider-color,
primary-color: dark.$primary-color,
danger-color: dark.$danger-color,
success-color: dark.$success-color,
hover-color: dark.$hover-color,
active-color: dark.$active-color
)
);
// 自定义函数:根据主题名和变量名,获取对应主题的变量值
@function get-theme-variable($theme, $key) {
// 校验主题名合法性
@if not map-has-key($theme-map, $theme) {
@error "主题名不存在,仅支持light/dark,当前传入:#{$theme}";
}
// 校验变量名合法性
@if not map-has-key(map-get($theme-map, $theme), $key) {
@error "主题变量不存在,当前主题:#{$theme},变量名:#{$key}";
}
// 返回对应变量值
@return map-get(map-get($theme-map, $theme), $key);
}
2.3 步骤2:全局样式入口配置(映射CSS变量)
在全局样式入口文件中,将SCSS主题变量映射为CSS全局变量(--变量名),通过body类名(.light/.dark)控制不同主题的CSS变量值,实现动态切换。
// src/styles/main.scss(全局样式入口,承接第一篇)
// 引入主题映射、公共变量、混合器、函数
@use './themes/theme-map' as theme;
@import './_variables.scss';
@import './_mixins.scss';
@import './_functions.scss';
// 1. 基础样式重置(统一浏览器默认样式,不随主题变化)
* {
margin: 0;
padding: 0;
box-sizing: border-box;
font-family: 'Microsoft YaHei', sans-serif;
}
// 2. 亮色主题:将SCSS变量映射为CSS全局变量
body.light {
--bg-color: #{theme.get-theme-variable(light, bg-color)};
--card-bg-color: #{theme.get-theme-variable(light, card-bg-color)};
--form-bg-color: #{theme.get-theme-variable(light, form-bg-color)};
--text-color: #{theme.get-theme-variable(light, text-color)};
--title-color: #{theme.get-theme-variable(light, title-color)};
--aux-text-color: #{theme.get-theme-variable(light, aux-text-color)};
--border-color: #{theme.get-theme-variable(light, border-color)};
--divider-color: #{theme.get-theme-variable(light, divider-color)};
--primary-color: #{theme.get-theme-variable(light, primary-color)};
--danger-color: #{theme.get-theme-variable(light, danger-color)};
--success-color: #{theme.get-theme-variable(light, success-color)};
--hover-color: #{theme.get-theme-variable(light, hover-color)};
--active-color: #{theme.get-theme-variable(light, active-color)};
}
// 3. 暗色主题:映射CSS全局变量(与亮色一一对应)
body.dark {
--bg-color: #{theme.get-theme-variable(dark, bg-color)};
--card-bg-color: #{theme.get-theme-variable(dark, card-bg-color)};
--form-bg-color: #{theme.get-theme-variable(dark, form-bg-color)};
--text-color: #{theme.get-theme-variable(dark, text-color)};
--title-color: #{theme.get-theme-variable(dark, title-color)};
--aux-text-color: #{theme.get-theme-variable(dark, aux-text-color)};
--border-color: #{theme.get-theme-variable(dark, border-color)};
--divider-color: #{theme.get-theme-variable(dark, divider-color)};
--primary-color: #{theme.get-theme-variable(dark, primary-color)};
--danger-color: #{theme.get-theme-variable(dark, danger-color)};
--success-color: #{theme.get-theme-variable(dark, success-color)};
--hover-color: #{theme.get-theme-variable(dark, hover-color)};
--active-color: #{theme.get-theme-variable(dark, active-color)};
}
// 4. 全局样式:使用CSS变量(统一适配所有主题)
body {
background-color: var(--bg-color);
color: var(--text-color);
font-size: $font-size-base;
transition: background-color 0.3s ease, color 0.3s ease; // 过渡效果,避免闪屏
}
// 5. 通用组件样式:使用CSS变量(承接第一篇,适配主题切换)
.card {
background-color: var(--card-bg-color);
border: 1px solid var(--border-color);
border-radius: $border-radius-base;
padding: $spacing-md;
box-shadow: $shadow-base;
transition: background-color 0.3s ease;
}
.btn {
background-color: var(--primary-color);
color: #fff;
border: 1px solid transparent;
border-radius: $border-radius-base;
padding: 8px 16px;
cursor: pointer;
&:hover {
background-color: var(--hover-color);
}
&:active {
background-color: var(--active-color);
}
}
// 其他通用样式(表单、标题等),均使用CSS变量...
2.4 步骤3:Vue3响应式控制主题(封装工具函数)
封装主题控制工具函数,实现「主题切换」「初始化主题」「持久化存储」,便于全局调用,贴合Vue3组合式API开发。
// src/utils/theme.ts(新增主题工具文件)
// 主题类型定义(限制主题名,避免非法值)
type ThemeType = 'light' | 'dark';
/**
* 获取本地存储的主题
* 优先读取localStorage,无值则返回默认主题(light)
*/
const getStoredTheme = (): ThemeType => {
const storedTheme = localStorage.getItem('theme');
// 校验本地存储的主题是否合法,非法则返回默认主题
return (storedTheme as ThemeType) || 'light';
};
/**
* 设置主题
* @param theme 主题名(light/dark)
*/
export const setTheme = (theme: ThemeType) => {
// 1. 校验主题合法性
if (!['light', 'dark'].includes(theme)) {
console.error('主题名非法,仅支持light/dark');
return;
}
// 2. 保存主题到本地存储(持久化)
localStorage.setItem('theme', theme);
// 3. 设置body类名,切换主题样式
document.body.className = theme;
// 4. 可扩展:发送主题切换事件,供其他组件监听(如导航栏、设置面板)
window.dispatchEvent(new CustomEvent('theme-change', { detail: theme }));
};
/**
* 初始化主题
* 页面加载时调用,恢复用户上次选择的主题
*/
export const initTheme = () => {
const theme = getStoredTheme();
setTheme(theme);
};
/**
* 切换主题(亮色↔暗色)
*/
export const toggleTheme = () => {
const currentTheme = getStoredTheme();
const newTheme = currentTheme === 'light' ? 'dark' : 'light';
setTheme(newTheme);
return newTheme; // 返回新主题,便于组件更新状态
};
2.5 步骤4:封装主题切换组件(全局可用)
封装主题切换按钮组件,结合Vue3组合式API,实现主题切换交互,适配全局使用(如导航栏、设置面板)。
<template>
<button class="theme-switch" @click="handleToggle" :aria-label="`切换到${currentTheme === 'light' ? '暗色' : '亮色'}模式`">
<!-- 图标可替换为自己的图标(如Element Plus图标) -->
<span v-if="currentTheme === 'light'" class="icon">🌙</span>
<span v-else class="icon">☀️</span>
<span class="text">{{ currentTheme === 'light' ? '暗色模式' : '亮色模式' }}</span>
</button>
</template>
<script setup lang="ts">
import { ref, onMounted, watch } from 'vue';
import { initTheme, toggleTheme, getStoredTheme } from '@/utils/theme';
// 响应式存储当前主题
const currentTheme = ref<'light' | 'dark'>('light');
// 页面挂载时初始化主题
onMounted(() => {
initTheme();
currentTheme.value = getStoredTheme();
// 监听主题切换事件(其他地方切换主题时,同步更新组件状态)
window.addEventListener('theme-change', (e: CustomEvent) => {
currentTheme.value = e.detail;
});
});
// 切换主题
const handleToggle = () => {
currentTheme.value = toggleTheme();
};
</script>
<style lang="scss" scoped>
.theme-switch {
display: flex;
align-items: center;
gap: 8px;
background-color: var(--card-bg-color);
color: var(--text-color);
border: 1px solid var(--border-color);
border-radius: $border-radius-base;
padding: 6px 12px;
cursor: pointer;
transition: all 0.2s ease;
&:hover {
background-color: var(--hover-color);
color: #fff;
}
.icon {
font-size: 16px;
}
.text {
font-size: 14px;
}
}
</style>
2.6 步骤5:全局引入与使用
在Vue3入口文件中初始化主题,确保页面加载时就应用用户选择的主题:
// src/main.ts
import { createApp } from 'vue';
import App from './App.vue';
import '@/styles/main.scss'; // 引入全局样式
import { initTheme } from '@/utils/theme';
// 初始化主题(必须在mount之前调用)
initTheme();
const app = createApp(App);
app.mount('#app');
在需要使用主题切换按钮的地方(如导航栏)引入组件:
<template>
<nav class="navbar">
<div class="navbar-left">系统名称</div>
<div class="navbar-right">
<ThemeSwitch />
</div>
</nav>
</template>
<script setup lang="ts">
import ThemeSwitch from '@/components/ThemeSwitch.vue';
</script>
2.7 核心优势与扩展说明
-
无闪屏切换:通过CSS过渡效果(transition),避免主题切换时的样式闪屏,提升用户体验;
-
可扩展性强 :新增主题(如浅色模式、深色模式2),只需新增对应的
_xxx.scss文件,修改_theme-map.scss即可,无需修改其他代码; -
持久化存储:通过localStorage保存用户主题选择,页面刷新后仍保留,提升用户体验;
-
全局统一:所有组件样式均使用CSS变量,主题切换时全局样式自动同步,无需单独适配;
-
扩展建议:可结合Vuex/Pinia管理主题状态,实现跨组件主题状态共享(适合大型项目)。
3. SCSS与组件库适配:自定义Element Plus样式
项目中,通常会使用Element Plus(Vue3)等组件库提升开发效率,但组件库默认样式往往不符合设计规范,此时通过SCSS覆盖组件库的默认变量,可实现「组件库样式自定义」,无需修改组件库源码,且升级组件库后仍能保留自定义样式。
3.1 适配Element Plus(Vue3首选组件库)
Element Plus提供了完整的SCSS变量体系,通过覆盖这些变量,可自定义组件库的主色调、字体、圆角、间距等,核心思路是「引入组件库SCSS源文件 + 覆盖默认变量」。
步骤1:安装Element Plus及相关依赖
// 安装Element Plus
npm install element-plus
// 安装Element Plus图标(可选,按需引入)
npm install @element-plus/icons-vue
步骤2:创建Element Plus自定义变量文件
在SCSS工程化目录中,新增组件库自定义变量文件,集中覆盖Element Plus默认变量:
// src/styles/element-plus/_variables.scss(新增目录和文件)
// 1. 覆盖Element Plus默认变量(完整变量列表参考Element Plus官方文档)
// 核心变量:主色调、辅助色、字体、圆角、间距等
$--color-primary: #42b983; // 主色调(与主题色一致)
$--color-success: #67c23a; // 成功色
$--color-warning: #e6a600; // 警告色
$--color-danger: #f56c6c; // 危险色
$--color-info: #1890ff; // 信息色
// 字体相关
$--font-size-base: 14px; // 基础字体大小(与全局变量一致)
$--font-family: 'Microsoft YaHei', sans-serif; // 字体(与全局一致)
// 圆角相关
$--border-radius-base: 8px; // 基础圆角(与全局变量一致)
$--border-radius-small: 4px; // 小圆角
$--border-radius-large: 12px; // 大圆角
// 按钮相关
$--button-height-base: 40px; // 基础按钮高度
$--button-padding-horizontal-base: 16px; // 按钮水平内边距
$--button-font-size-base: 14px; // 按钮字体大小
// 输入框相关
$--input-height-base: 40px; // 输入框高度
$--input-border-radius: 8px; // 输入框圆角
// 卡片相关
$--card-border-radius: 8px; // 卡片圆角
$--card-shadow: 0 2px 8px rgba(0, 0, 0, 0.08); // 卡片阴影
// 2. 引入Element Plus的SCSS源文件(关键:必须在变量覆盖之后引入)
// 注意:引入的是src目录下的scss文件,不是编译后的css文件
@use 'element-plus/theme-chalk/src/common/var.scss' with (
// 覆盖Element Plus的颜色变量(与上面的变量对应)
$colors: (
primary: (
base: $--color-primary,
),
success: (
base: $--color-success,
),
warning: (
base: $--color-warning,
),
danger: (
base: $--color-danger,
),
info: (
base: $--color-info,
),
),
// 覆盖字体变量
$font-size: (
base: $--font-size-base,
),
// 覆盖圆角变量
$border-radius: (
base: $--border-radius-base,
small: $--border-radius-small,
large: $--border-radius-large,
),
// 覆盖按钮变量
$button: (
height: (
base: $--button-height-base,
),
padding: (
horizontal: (
base: $--button-padding-horizontal-base,
),
),
font-size: (
base: $--button-font-size-base,
),
),
);
// 3. 额外自定义Element Plus组件样式(补充变量无法覆盖的样式)
// 示例:自定义按钮hover效果
.el-button {
&:hover {
transform: translateY(-1px);
transition: transform 0.2s ease, background-color 0.2s ease;
}
}
// 自定义输入框聚焦效果
.el-input__inner:focus {
box-shadow: 0 0 0 2px rgba($--color-primary, 0.2);
border-color: $--color-primary;
}
// 自定义卡片样式
.el-card {
border: 1px solid var(--border-color); // 结合主题CSS变量
}
步骤3:Vite配置全局注入(避免重复引入)
修改Vite配置,将Element Plus自定义变量文件全局注入,确保所有组件都能使用自定义后的样式:
// vite.config.ts(承接第一篇,补充配置)
import { defineConfig } from 'vite';
import vue from '@vitejs/plugin-vue';
import path from 'path';
// 按需引入Element Plus组件(可选,减少打包体积)
import AutoImport from 'unplugin-auto-import/vite';
import Components from 'unplugin-vue-components/vite';
import { ElementPlusResolver } from 'unplugin-vue-components/resolvers';
export default defineConfig({
plugins: [
vue(),
// 按需引入Element Plus(推荐,减少打包体积)
AutoImport({
resolvers: [ElementPlusResolver()],
}),
Components({
resolvers: [ElementPlusResolver()],
}),
],
css: {
preprocessorOptions: {
scss: {
additionalData: `
// 全局注入:Element Plus自定义变量、全局变量、混合器
@import "@/styles/element-plus/_variables.scss";
@import "@/styles/_variables.scss";
@import "@/styles/_mixins.scss";
`,
},
},
},
resolve: {
alias: {
'@': path.resolve(__dirname, './src'),
},
},
});
步骤4:入口文件引入Element Plus
// src/main.ts(补充Element Plus引入)
import { createApp } from 'vue';
import App from './App.vue';
import '@/styles/main.scss';
import { initTheme } from '@/utils/theme';
// 引入Element Plus样式(无需单独引入css,已通过SCSS注入)
import ElementPlus from 'element-plus';
initTheme();
const
app.use(ElementPlus); // 全局引入Element Plus(按需引入可省略此步) app.mount('#app');
注意事项:
-
若使用按需引入(推荐),无需在main.ts中全局引入ElementPlus,unplugin-auto-import和unplugin-vue-components会自动引入使用到的组件和样式;
-
覆盖Element Plus变量时,需确保变量名与官方一致(参考Element Plus官方SCSS变量文档),避免覆盖失效;
-
额外自定义组件样式时,建议使用组件库自带的类名(如.el-button、.el-input),避免使用深度选择器(::v-deep),提升样式优先级的同时保证兼容性。
4. SCSS优化技巧:压缩CSS、消除冗余、提升编译速度
大型项目中,SCSS样式文件往往体积较大,若不进行优化,会导致打包体积增大、页面加载变慢、编译速度下降,影响开发效率和用户体验。
4.1 消除样式冗余:精简代码,避免重复
样式冗余是项目开发中最常见的问题,尤其是多团队协作时,容易出现重复样式、无用样式,以下是针对性解决方案。
技巧1:复用混合器和自定义函数
将高频使用的样式逻辑(如按钮样式、卡片样式、间距控制)封装为混合器,将复杂计算逻辑封装为自定义函数,避免重复书写,同时保证样式统一。
示例:封装全局阴影混合器,适配不同场景的阴影需求:
// src/styles/_mixins.scss(新增阴影混合器)
@mixin shadow($depth: 1) {
@if $depth == 1 {
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05); // 浅阴影(卡片默认)
} @else if $depth == 2 {
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.08); // 中阴影(弹窗、下拉菜单)
} @else if $depth == 3 {
box-shadow: 0 8px 16px rgba(0, 0, 0, 0.12); // 深阴影(模态框)
}
}
// 使用示例
.card {
@include shadow(1); // 卡片浅阴影
}
.modal {
@include shadow(3); // 模态框深阴影
}
技巧2:使用@extend继承样式
对于结构相似、仅少量样式不同的元素,使用@extend继承基础样式,减少重复代码。注意:@extend适合继承静态样式,不适合带参数的动态样式(动态样式用混合器)。
// 基础文本样式(可继承)
.base-text {
font-size: 14px;
line-height: 1.5;
color: var(--text-color);
}
// 继承基础样式,补充特殊样式
.aux-text {
@extend .base-text;
color: var(--aux-text-color); // 仅修改文本色
}
.title-text {
@extend .base-text;
font-size: 16px;
font-weight: 600; // 仅修改字体大小和粗细
}
技巧3:删除无用样式(使用工具检测)
项目迭代过程中,容易出现无用样式(如删除组件后未删除对应的样式),可使用工具检测并删除,减少样式体积:
-
开发环境:使用
purgecss(Vite插件:vite-plugin-purgecss),自动检测未使用的样式并删除; -
代码审查:定期使用
stylelint检查冗余样式,规范开发习惯。
// vite.config.ts(配置purgecss插件)
import { defineConfig } from 'vite';
import vue from '@vitejs/plugin-vue';
import PurgeCSS from 'vite-plugin-purgecss';
export default defineConfig({
plugins: [
vue(),
// 配置purgecss,自动删除无用样式
PurgeCSS({
content: ['./index.html', './src/**/*.vue'], // 检测的文件路径
safelist: ['html', 'body'], // 保留的样式类名(避免误删全局样式)
}),
],
});
4.2 压缩CSS:减小打包体积
SCSS编译为CSS后,可通过压缩工具减小文件体积,提升页面加载速度,Vite默认支持CSS压缩,可通过配置优化压缩效果。
// vite.config.ts(配置CSS压缩)
import { defineConfig } from 'vite';
import vue from '@vitejs/plugin-vue';
export default defineConfig({
plugins: [vue()],
build: {
minify: 'terser', // 开启压缩(默认开启,生产环境生效)
cssCodeSplit: true, // 拆分CSS文件(按组件拆分,避免单个CSS文件过大)
rollupOptions: {
output: {
// 对CSS文件进行哈希命名,便于缓存
assetFileNames: {
css: 'css/[name].[hash].css',
},
},
},
},
css: {
minify: true, // 开启CSS压缩
},
});
4.3 提升SCSS编译速度:优化工程化配置
大型项目中,SCSS文件较多时,编译速度会明显下降,可通过以下配置优化,提升开发效率。
技巧1:模块化拆分,避免大文件
将SCSS文件按功能拆分(如变量、混合器、函数、主题、组件库适配),避免单个文件过大,减少编译时的解析时间(参考前文的工程化目录结构)。
技巧2:合理使用@use和@import
SCSS中@use比@import更高效(@use不会重复引入文件),建议优先使用@use引入文件,尤其是全局变量、混合器等高频使用的文件。
// 推荐使用@use(不重复引入)
@use './_variables.scss' as vars;
@use './_mixins.scss' as mixins;
// 不推荐大量使用@import(可能重复引入)
// @import './_variables.scss';
// @import './_mixins.scss';
技巧3:缓存编译结果
使用Vite的缓存机制,缓存SCSS编译结果,避免每次开发时重新编译所有SCSS文件,Vite默认开启缓存,可通过配置优化缓存策略:
// vite.config.ts(优化缓存)
import { defineConfig } from 'vite';
import vue from '@vitejs/plugin-vue';
export default defineConfig({
plugins: [vue()],
cacheDir: './node_modules/.vite/scss-cache', // 自定义SCSS缓存目录
optimizeDeps: {
include: ['@/styles/_variables.scss', '@/styles/_mixins.scss'], // 提前优化依赖
},
});
5. SCSS最佳实践
5.1 目录结构规范化(统一标准)
延续前文的目录结构,进一步细化,适配多团队协作(如按业务模块拆分样式),确保每个团队都能清晰找到对应文件:
src/styles/
├── core/ // 核心样式(全局通用,不随业务变化)
│ ├── _variables.scss // 全局公共变量(字体、圆角、间距等)
│ ├── _mixins.scss // 全局混合器(按钮、阴影、响应式等)
│ ├── _functions.scss // 自定义函数(颜色转换、尺寸计算等)
│ └── _reset.scss // 基础样式重置(统一浏览器默认样式)
├── themes/ // 主题相关(全局主题切换)
│ ├── _light.scss // 亮色主题变量
│ ├── _dark.scss // 暗色主题变量
│ └── _theme-map.scss // 主题映射表
├── component-lib/ // 组件库适配(按组件库拆分)
│ ├── element-plus/ // Element Plus适配
│ │ └── _variables.scss
│ └── ant-design-vue/ // Ant Design Vue适配
│ └── _variables.scss
├── business/ // 业务模块样式(按团队/业务拆分)
│ ├── user/ // 用户模块(用户团队维护)
│ │ └── _user.scss
│ ├── order/ // 订单模块(订单团队维护)
│ │ └── _order.scss
│ └── common/ // 业务公共样式(多团队共用)
│ └── _business-common.scss
└── main.scss // 样式入口(统一引入所有核心样式)
5.2 命名规范(避免冲突)
多团队协作时,样式类名容易冲突,需制定统一的命名规范,推荐使用「BEM命名规范」(Block-Element-Modifier),清晰区分组件、元素和状态。
// BEM命名规范:Block__Element--Modifier
// Block:组件名(如user-card)
// Element:组件内元素(如title、content)
// Modifier:组件状态(如disabled、active)
// 示例:用户卡片组件样式
.user-card { // Block:用户卡片
@include mixins.shadow(1);
background-color: var(--card-bg-color);
border-radius: vars.$border-radius-base;
padding: vars.$spacing-md;
&__title { // Element:卡片标题
font-size: 16px;
font-weight: 600;
color: var(--title-color);
}
&__content { // Element:卡片内容
@include mixins.base-text;
margin-top: vars.$spacing-sm;
}
&--disabled { // Modifier:禁用状态
opacity: 0.7;
cursor: not-allowed;
}
&--active { // Modifier:选中状态
border: 1px solid var(--primary-color);
}
}
5.3 变量管理规范化(统一配置)
-
全局公共变量(如字体、圆角、间距)统一放在
core/_variables.scss,由架构团队维护,不允许各团队随意修改; -
业务模块变量(如业务专属颜色)放在对应业务目录下,命名需加业务前缀(如
$order-primary-color),避免与全局变量冲突; -
主题变量统一放在
themes目录,由UI团队维护,确保主题色、样式统一。
5.4 代码审查与规范校验(强制约束)
通过工具强制约束SCSS代码规范,避免不规范代码提交,提升代码质量:
-
使用
stylelint配置SCSS规范(如禁止使用!important、强制使用BEM命名、禁止重复样式); -
配置pre-commit钩子(如husky),提交代码时自动校验SCSS规范,不通过则无法提交;
-
定期开展代码审查,检查样式冗余、命名不规范等问题,形成迭代优化机制。
// .stylelintrc.js(SCSS规范配置)
module.exports = {
root: true,
plugins: ['stylelint-scss'],
rules: {
// 禁止使用!important
'declaration-no-important': true,
// 强制使用BEM命名规范(简化版)
'selector-class-pattern': '^[a-z0-9_-]+(__[a-z0-9_-]+)?(--[a-z0-9_-]+)?$',
// 禁止重复样式
'no-duplicate-selectors': true,
// 禁止空规则
'no-empty-source': true,
// SCSS相关规则
'scss/at-import-no-partial-leading-underscore': null,
'scss/at-import-partial-extension': ['always', { extension: 'scss' }],
},
};
5.5 版本控制与迭代(追溯变更)
多团队协作时,SCSS文件的变更需做好版本控制,避免冲突和误修改:
-
使用Git进行版本控制,每次修改SCSS文件时,提交信息需清晰(如「feat: 新增订单模块卡片样式」「fix: 修复按钮hover样式异常」);
-
核心样式文件(如全局变量、混合器)的修改,需经过架构团队审核,避免影响所有团队;
-
定期同步样式变更,告知所有团队,确保各团队使用的样式版本一致。