Vue3 项目 SCSS 通用样式 —— Reset、工具类、主题与工程化模板

每个 Vue3 项目启动后第一件事往往是写一大堆全局样式:重置默认行为、封装布局工具类、定义主题变量……这些代码复用率极高,但每次都从头写一遍很低效。本文整理了一份开箱即用的 SCSS 通用样式集,所有值均通过变量、Map、Mixin 驱动,覆盖 Reset、布局、间距、文字、主题、响应式、动画等高频场景,复制进项目就能用。

一、全局变量 —— 所有值的唯一来源

分两个文件:_vars-only.scss 存放纯 Map 和变量(不产生 CSS),供 additionalData 注入;variables.scss 在此基础上输出 CSS 自定义属性,由 index.scss 一次性引入。

styles/_vars-only.scss

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
// styles/_vars-only.scss
// ═══════════════════════════════════════
// 仅 Map + 变量,不输出任何 CSS
// Vite 通过 additionalData 自动注入
// ═══════════════════════════════════════

// ====== 主题色 Map ======
$theme-colors: (
"primary": #1677ff,
"success": #52c41a,
"warning": #faad14,
"danger": #ff4d4f,
"info": #909399,
);

// ====== 语义色 Map ======
$semantic-colors: (
"bg-page": #f5f5f5,
"bg-white": #fff,
"text": #333,
"text-secondary": #666,
"text-disabled": #bbb,
"text-placeholder": #c0c4cc,
"border": #eee,
"border-light": #f0f0f0,
);

// ====== 暗色主题覆盖 Map ======
$dark-theme-colors: (
"bg-page": #141414,
"bg-white": #1e1e1e,
"text": #e5e5e5,
"text-secondary": #999,
"text-disabled": #555,
"border": #333,
"border-light": #2a2a2a,
);

// ====== 字体 Map ======
$font-family-base: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif;
$font-size-base: 14px;
$font-sizes: (10, 12, 13, 14, 15, 16, 18, 20, 22, 24, 28, 32, 36, 40, 48);
$font-weights: (
"normal": 400,
"medium": 500,
"semibold": 600,
"bold": 700,
);
$line-heights: (
"tight": 1.25,
"normal": 1.5,
"relaxed": 1.75,
);

// ====== 圆角 Map ======
$radius-map: (
"sm": 4px,
"base": 8px,
"md": 12px,
"lg": 16px,
"full": 50%,
);

// ====== 间距 Map ======
$spacing-sizes: (0, 2, 4, 6, 8, 10, 12, 14, 16, 20, 24);
$spacing-base: 16px;

// ====== 阴影 Map ======
$shadow-map: (
"sm": 0 1px 3px rgba(0, 0, 0, 0.08),
"base": 0 2px 8px rgba(0, 0, 0, 0.1),
"lg": 0 4px 16px rgba(0, 0, 0, 0.12),
);

// ====== 过渡变量 ======
$transition-duration: 0.3s;
$transition-timing: ease;
$transition-duration-base: $transition-duration;
$transition-base: all $transition-duration $transition-timing;

// ====== 断点 Map ======
$breakpoints: (
"sm": 576px,
"md": 768px,
"lg": 992px,
"xl": 1200px,
"xxl": 1400px,
);

// ====== SCSS 变量(引用 Map 值) ======
$color-primary: map-get($theme-colors, "primary");
$color-success: map-get($theme-colors, "success");
$color-warning: map-get($theme-colors, "warning");
$color-danger: map-get($theme-colors, "danger");
$color-info: map-get($theme-colors, "info");

$color-bg-page: map-get($semantic-colors, "bg-page");
$color-bg-white: map-get($semantic-colors, "bg-white");
$color-text: map-get($semantic-colors, "text");
$color-text-secondary: map-get($semantic-colors, "text-secondary");
$color-text-disabled: map-get($semantic-colors, "text-disabled");
$color-text-placeholder: map-get($semantic-colors, "text-placeholder");
$color-border: map-get($semantic-colors, "border");
$color-border-light: map-get($semantic-colors, "border-light");

$radius-sm: map-get($radius-map, "sm");
$radius-base: map-get($radius-map, "base");
$radius-md: map-get($radius-map, "md");
$radius-lg: map-get($radius-map, "lg");
$radius-full: map-get($radius-map, "full");

$shadow-sm: map-get($shadow-map, "sm");
$shadow-base: map-get($shadow-map, "base");
$shadow-lg: map-get($shadow-map, "lg");

styles/variables.scss

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
// styles/variables.scss
// 发布 CSS 自定义属性(_vars-only 的变量已由 additionalData 全局注入)

// ====== CSS 自定义属性(运行时切换) ======
:root {
// 主题色
@each $name, $color in $theme-colors {
--color-#{$name}: #{$color};
}
// 语义色
@each $name, $color in $semantic-colors {
--color-#{$name}: #{$color};
}
// 圆角
@each $name, $value in $radius-map {
--radius-#{$name}: #{$value};
}
// 阴影
@each $name, $value in $shadow-map {
--shadow-#{$name}: #{$value};
}
--font-size-base: #{$font-size-base};
--transition-base: #{$transition-base};
}

// 暗色主题
[data-theme="dark"] {
@each $name, $color in $dark-theme-colors {
--color-#{$name}: #{$color};
}
}

二、CSS Reset —— 变量驱动的基础重置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
// styles/reset.scss
*,
*::before,
*::after {
margin: 0;
padding: 0;
box-sizing: border-box;
}

html {
-webkit-text-size-adjust: 100%;
-webkit-tap-highlight-color: transparent;
}

body {
font-family: $font-family-base;
font-size: $font-size-base;
line-height: map-get($line-heights, "normal");
color: var(--color-text);
background-color: var(--color-bg-page);
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}

a {
color: inherit;
text-decoration: none;
}

img {
max-width: 100%;
height: auto;
vertical-align: middle;
}

ul,
ol {
list-style: none;
}

button,
input,
select,
textarea {
font: inherit;
color: inherit;
outline: none;
border: none;
background: none;
}

table {
border-collapse: collapse;
border-spacing: 0;
}

三、Mixin 库 —— 全部参数化封装

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
// styles/mixins.scss
@use "sass:math";

// ====== Flex 布局 ======
@mixin flex($direction: row, $justify: flex-start, $align: stretch, $gap: 0) {
display: flex;
flex-direction: $direction;
justify-content: $justify;
align-items: $align;
gap: $gap;
}

// ====== 文本省略 ======
@mixin ellipsis($line: 1) {
@if $line == 1 {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
} @else {
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: $line;
overflow: hidden;
text-overflow: ellipsis;
}
}

// ====== 清除浮动 ======
@mixin clearfix {
&::after {
content: "";
display: block;
clear: both;
}
}

// ====== 1px 边框(移动端高清屏) ======
@mixin hairline($direction: "bottom", $color: $color-border, $offset: 0) {
position: relative;
&::after {
content: "";
position: absolute;
#{$direction}: $offset;
left: 0;
width: 100%;
height: 1px;
background-color: $color;
transform: scaleY(0.5);
@if $direction == "top" {
bottom: auto;
}
}
}

// ====== 安全区域适配 ======
@mixin safe-area-padding($positions...) {
@each $pos in $positions {
padding-#{$pos}: constant(safe-area-inset-#{$pos});
padding-#{$pos}: env(safe-area-inset-#{$pos});
}
}

// ====== 点击态 ======
@mixin active-opacity($opacity: 0.7) {
&:active {
opacity: $opacity;
}
}

// ====== 卡片容器(属性全用变量) ======
@mixin card($bg: var(--color-bg-white), $radius: var(--radius-base), $padding: $spacing-base, $shadow: var(--shadow-base)) {
background: $bg;
border-radius: $radius;
padding: $padding;
box-shadow: $shadow;
}

// ====== 滚动条美化 ======
@mixin custom-scrollbar($width: 6px, $thumb-color: $color-border-light, $track-color: transparent) {
&::-webkit-scrollbar {
width: $width;
height: $width;
}
&::-webkit-scrollbar-thumb {
background: $thumb-color;
border-radius: $width;
}
&::-webkit-scrollbar-track {
background: $track-color;
}
}

四、响应式断点 —— Map + Mixin 语义化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
// styles/breakpoints.scss

// 宽屏 (≥)
@mixin respond-to($key) {
$width: map-get($breakpoints, $key);
@if $width {
@media (min-width: $width) {
@content;
}
}
}

// 窄屏 (≤)
@mixin respond-below($key) {
$width: map-get($breakpoints, $key);
@if $width {
@media (max-width: $width) {
@content;
}
}
}

// 区间
@mixin respond-between($min, $max) {
$min-w: map-get($breakpoints, $min);
$max-w: map-get($breakpoints, $max);
@if $min-w and $max-w {
@media (min-width: $min-w) and (max-width: $max-w) {
@content;
}
}
}

五、布局工具类 —— Map 循环批量生成

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
// styles/layout.scss
// 组合类 .flex-{justify}-{align} —— 一个类同时控制分布与对齐
@each $justify-name, $justify-value in $flex-justify {
@each $item-name, $item-value in $flex-items {
.flex-#{$justify-name}-#{$item-name} {
display: flex;
justify-content: $justify-value;
align-items: $item-value;
}
}
}

// ====== Grid 布局(Map 循环) ======
$grid-cols: (2, 3, 4);
@each $n in $grid-cols {
.grid-#{$n} {
display: grid;
grid-template-columns: repeat($n, 1fr);
gap: $spacing-base * 0.75;
}
}

// ====== 定位(Map 循环) ======
$positions: (relative, absolute, fixed, sticky);
@each $pos in $positions {
.#{$pos} {
position: $pos;
}
}

六、间距工具类 —— 一套 Mixin + Map 批量生成

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
// styles/spacing.scss

$spacing-directions: (
"": "",
"t": "top",
"r": "right",
"b": "bottom",
"l": "left",
"x": (
"left",
"right",
),
"y": (
"top",
"bottom",
),
);

@mixin gen-spacing($property, $abbr) {
@each $size in $spacing-sizes {
@each $dir-abbr, $dir-prop in $spacing-directions {
.#{$abbr}#{$dir-abbr}-#{$size} {
@if type-of($dir-prop) == "list" {
@each $d in $dir-prop {
#{$property}-#{$d}: #{$size}px;
}
} @else if $dir-prop == "" {
#{$property}: #{$size}px;
} @else {
#{$property}-#{$dir-prop}: #{$size}px;
}
}
}
}
}
// 生成 .m-4, .mt-8, .mx-12... 和 .p-4, .pt-8, .px-12...
@include gen-spacing("margin", "m");
@include gen-spacing("padding", "p");

七、文字样式 —— 全部 Map + 循环驱动

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
// styles/typography.scss

// ====== 字号 ======
@each $size in $font-sizes {
.text-#{$size} {
font-size: #{$size}px;
}
}

// ====== 字重 ======
@each $name, $weight in $font-weights {
.font-#{$name} {
font-weight: $weight;
}
}

// ====== 行高 ======
@each $name, $lh in $line-heights {
.leading-#{$name} {
line-height: $lh;
}
}

// ====== 文字颜色(自动跟随主题切换) ======
@each $name, $color in $theme-colors {
.text-#{$name} {
color: var(--color-#{$name});
}
}
$text-color-suffixes: (
"secondary": var(--color-text-secondary),
"disabled": var(--color-text-disabled),
"placeholder": var(--color-text-placeholder),
);
@each $suffix, $value in $text-color-suffixes {
.text-#{$suffix} {
color: $value;
}
}

// ====== 文字对齐 ======
$text-aligns: (left, center, right);
@each $align in $text-aligns {
.text-#{$align} {
text-align: $align;
}
}

// ====== 文字省略工具类 ======
@each $line in (1, 2, 3) {
.text-ellipsis-#{$line} {
@if $line == 1 {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
} @else {
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: $line;
overflow: hidden;
text-overflow: ellipsis;
}
}
}
.text-ellipsis {
@extend .text-ellipsis-1;
}

八、背景 / 边框 / 圆角 —— Map 循环 + 变量

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
// styles/colors.scss

// ====== 背景色 ======
$bg-color-suffixes: (
"white": var(--color-bg-white),
"page": var(--color-bg-page),
);
@each $suffix, $value in $bg-color-suffixes {
.bg-#{$suffix} {
background-color: $value;
}
}
@each $name, $color in $theme-colors {
.bg-#{$name} {
background-color: var(--color-#{$name});
color: var(--color-bg-white);
}
}

// ====== 边框 ======
$border-width: 1px;
.border {
border: $border-width solid $color-border;
}
.border-t {
border-top: $border-width solid $color-border;
}
.border-b {
border-bottom: $border-width solid $color-border;
}
.border-l {
border-left: $border-width solid $color-border;
}
.border-r {
border-right: $border-width solid $color-border;
}
.border-none {
border: none;
}

// ====== 圆角 ======
@each $name, $value in $radius-map {
.rounded-#{$name} {
border-radius: $value;
}
}

九、视觉类 —— 阴影 / 光标 / 溢出 / 可见性

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
// styles/visual.scss

// ====== 阴影(Map 循环 + CSS 变量) ======
@each $name, $value in $shadow-map {
.shadow-#{$name} {
box-shadow: var(--shadow-#{$name});
}
}
.shadow {
@extend .shadow-base;
}

// ====== 光标 ======
$cursors: (
"pointer": pointer,
"not-allowed": not-allowed,
);
@each $name, $value in $cursors {
.cursor-#{$name} {
cursor: $value;
}
}
.pointer-events-none {
pointer-events: none;
}

// ====== 溢出 ======
$overflows: ("hidden", "auto", "scroll");
@each $val in $overflows {
.overflow-#{$val} {
overflow: $val;
}
}
.overflow-x-auto {
overflow-x: auto;
}
.overflow-y-auto {
overflow-y: auto;
}

// ====== 可见性 ======
.hidden {
display: none;
}
.invisible {
visibility: hidden;
}

十、滚动条美化

1
2
3
4
5
6
7
8
9
10
11
// styles/scrollbar.scss

// 全局滚动条
html {
@include custom-scrollbar();
}

// 局部滚动条
.scroll-container {
@include custom-scrollbar(4px, rgba($color-text, 0.15));
}

十一、过渡与动画

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
// styles/transitions.scss

// ====== 五套预置动画 ======
// 淡入淡出
.fade-enter-active,
.fade-leave-active {
transition: opacity $transition-duration-base $transition-timing;
}
.fade-enter-from,
.fade-leave-to {
opacity: 0;
}

// 从底部滑入
.slide-up-enter-active,
.slide-up-leave-active {
transition:
transform $transition-duration-base $transition-timing,
opacity $transition-duration-base $transition-timing;
}
.slide-up-enter-from,
.slide-up-leave-to {
transform: translateY(100%);
opacity: 0;
}

// 缩放
.scale-enter-active,
.scale-leave-active {
transition:
transform $transition-duration-base $transition-timing,
opacity $transition-duration-base $transition-timing;
}
.scale-enter-from,
.scale-leave-to {
transform: scale(0.9);
opacity: 0;
}

// 页面切换
.page-enter-active {
transition:
opacity $transition-duration-base $transition-timing,
transform $transition-duration-base $transition-timing;
}
.page-leave-active {
transition: opacity ($transition-duration-base * 0.67) $transition-timing;
}
.page-enter-from {
opacity: 0;
transform: translateY(10px);
}
.page-leave-to {
opacity: 0;
}

// ====== 常用过渡工具类 ======
.transition-base {
transition: $transition-base;
}
.transition-fade {
transition: opacity $transition-duration-base $transition-timing;
}
.transition-slide {
transition: transform $transition-duration-base $transition-timing;
}

十二、移动端适配

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// styles/mobile.scss

// 安全区
$safe-area-sides: (top, bottom, left, right);
@each $side in $safe-area-sides {
.safe-#{$side} {
padding-#{$side}: constant(safe-area-inset-#{$side});
padding-#{$side}: env(safe-area-inset-#{$side});
}
}

// 防橡皮筋
.no-bounce {
overscroll-behavior: none;
}

// iOS 输入框缩放修复
$ios-input-min-font: 16px;
.fix-ios-zoom {
font-size: $ios-input-min-font;
}

十三、通用组件样式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
// styles/components.scss

// ====== 空状态 ======
$empty-state-config: (
"padding-y": 60px,
"icon-size": 48px,
"icon-mb": 12px,
"icon-opacity": 0.4,
"text-size": 14px,
);

.empty-state {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: map-get($empty-state-config, "padding-y") 0;
color: $color-text-secondary;

&__icon {
font-size: map-get($empty-state-config, "icon-size");
margin-bottom: map-get($empty-state-config, "icon-mb");
opacity: map-get($empty-state-config, "icon-opacity");
}

&__text {
font-size: map-get($empty-state-config, "text-size");
}
}

// ====== 骨架屏 ======
$skeleton-duration: 1.5s;
$skeleton-start: #f0f0f0;
$skeleton-mid: #e0e0e0;
$skeleton-end: #f0f0f0;
$skeleton-radius: map-get($radius-map, "sm");

.skeleton {
background: linear-gradient(90deg, $skeleton-start 25%, $skeleton-mid 50%, $skeleton-end 75%);
background-size: 200% 100%;
animation: skeleton-shimmer $skeleton-duration infinite;
border-radius: $skeleton-radius;
}

@keyframes skeleton-shimmer {
0% {
background-position: 200% 0;
}
100% {
background-position: -200% 0;
}
}

// ====== 分割线 ======
.divider {
height: 1px;
background-color: $color-border;
margin: $spacing-base 0;
}

// ====== 标签 ======
$tag-config: (
"padding-x": 8px,
"padding-y": 2px,
"font-size": 12px,
"radius": 4px,
"line-height": 20px,
"bg-opacity": 0.1,
);

.tag {
display: inline-flex;
align-items: center;
padding: map-get($tag-config, "padding-y") map-get($tag-config, "padding-x");
font-size: map-get($tag-config, "font-size");
border-radius: map-get($tag-config, "radius");
line-height: map-get($tag-config, "line-height");

@each $name, $color in $theme-colors {
&--#{$name} {
color: $color;
background: rgba($color, map-get($tag-config, "bg-opacity"));
}
}
}

十四、全局入口文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// styles/index.scss

@import "./variables.scss";
@import "./mixins.scss";
@import "./breakpoints.scss";
@import "./reset.scss";
@import "./layout.scss";
@import "./spacing.scss";
@import "./typography.scss";
@import "./colors.scss";
@import "./visual.scss";
@import "./scrollbar.scss";
@import "./transitions.scss";
@import "./mobile.scss";
@import "./components.scss";

main.ts

1
2
3
4
5
import { createApp } from "vue";
import App from "./App.vue";
import "@/styles/index.scss";

createApp(App).mount("#app");

十五、Vite 配置 —— additionalData 自动注入

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// vite.config.ts
import { defineConfig } from "vite";
import vue from "@vitejs/plugin-vue";
import { resolve } from "path";

export default defineConfig({
plugins: [vue()],
resolve: { alias: { "@": resolve(__dirname, "src") } },
css: {
preprocessorOptions: {
scss: {
additionalData: `
@use "@/styles/_vars-only.scss" as *;
@use "@/styles/mixins.scss" as *;
@use "@/styles/breakpoints.scss" as *;
`,
},
},
},
});

配置 additionalData 后,每个 Vue 组件 <style scoped lang="scss"> 内无需手动 @import,可直接使用变量和 Mixin。

十六、文件目录一览

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
src/
└── styles/
├── index.scss # 总入口
├── _vars-only.scss # 纯变量 Map(不输出 CSS,供 additionalData 注入)
├── variables.scss # 引入 _vars-only + 输出 CSS 自定义属性
├── mixins.scss # Mixin 库(全部参数化)
├── breakpoints.scss # 响应式 Mixin(Map 驱动)
├── reset.scss # 浏览器重置(引用变量)
├── layout.scss # Flex / Grid / 定位(Map 循环)
├── spacing.scss # margin / padding(Mixin + Map)
├── typography.scss # 文字工具类(Map 循环)
├── colors.scss # 背景 / 边框 / 圆角(Map 循环 + 变量)
├── visual.scss # 阴影 / 光标 / 溢出 / 可见性(Map 循环)
├── scrollbar.scss # 滚动条样式
├── transitions.scss # 动画(变量驱动)
├── mobile.scss # 移动端适配(变量集中)
└── components.scss # 通用组件样式(Map + Mixin)

十七、日常使用

组件中直接用工具类名:

1
2
3
4
5
6
7
8
9
10
<template>
<div class="flex-between px-16 py-12 bg-white rounded-base shadow">
<span class="text-16 font-semibold text-ellipsis flex-1">标题文字</span>
<span class="text-12 text-secondary cursor-pointer ml-12">更多</span>
</div>

<Transition name="slide-up">
<div v-if="visible" class="popup">...</div>
</Transition>
</template>

SCSS 中引用变量和 Mixin(前提:已配置 additionalData):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<style scoped lang="scss">
.nav-bar {
@include flex($justify: space-between, $align: center, $gap: 16px);
}

.card {
@include card;
@include active-opacity;

&__title {
@include ellipsis(2);
color: $color-primary;
}

&__footer {
margin-top: $spacing-base * 0.75;
padding-top: $spacing-base * 0.75;
border-top: 1px solid $color-border;
}
}
</style>

变量体系一览:

层级形式用途
Map$theme-colors $shadow-map $radius-map分组存放关联值,供循环遍历和取值
SCSS 变量$color-primary $spacing-base $transition-duration编译时引用,一处修改全局生效
CSS 自定义属性var(--color-primary) var(--shadow-base)运行时切换暗色模式,Map 循环自动生成
Mixin@include card @include ellipsis(2)参数化复用,默认值来自变量

本文所有代码均为可直接复制使用的生产级片段。下次开新项目时,把 styles/ 目录整体复制,修改 variables.scss 中的 Map 值即可适配新品牌的视觉规范。