本文档提供了基于Vue2架构搭建项目的详细指南,所有样式基于UItoken.md文件定义的标准实现。
项目整体架构如下:
- 前端框架:Vue2
- 构建工具:Vue CLI 4
- 样式方案:CSS变量 + SCSS + Tailwind CSS (按需定制)
- 组件库:Element UI (可选)
- 图表库:ECharts
为确保与Vue2兼容,推荐使用以下版本的依赖:
# Vue2最新稳定版本
vue@2.7.14
# Vue CLI
@vue/cli@4.5.19
# Vue CLI服务
@vue/cli-service@4.5.19# SCSS支持
sass@1.32.13
sass-loader@10.4.1# Tailwind CSS (与Vue2兼容的版本)
tailwindcss@3.3.3
postcss@8.4.24
autoprefixer@10.4.14
postcss-loader@4.3.0# Element UI for Vue2
element-ui@2.15.14"dependencies": {
"core-js": "^3.8.3",
"element-ui": "^2.15.14",
"vue": "^2.7.14"
},
"devDependencies": {
"@vue/cli-plugin-babel": "~4.5.19",
"@vue/cli-service": "~4.5.19",
"autoprefixer": "^10.4.14",
"postcss": "^8.4.24",
"postcss-loader": "^4.3.0",
"sass": "^1.32.13",
"sass-loader": "^10.4.1",
"tailwindcss": "^3.3.3",
"vue-template-compiler": "^2.7.14"
}采用三层架构设计的样式系统,实现主题可定制、样式统一:
包含所有原始值的定义,如色阶、字体大小、间距值等
/* 基础色板示例 src/styles/tokens/base-colors.scss */
:root {
/* 基础色板 - 永远不变 */
--blue-color-1: #E3F4FF;
--blue-color-2: #C0E6FD;
--blue-color-3: #80CAFF;
/* ...其他基础色 */
}将业务名称映射到基础层的具体项,不同主题下映射关系可变
/* 默认主题映射 src/styles/themes/default-theme.scss */
:root {
/* 业务色映射 - 可随主题变化 */
--hx-brand-color-1: var(--blue-color-1);
--hx-brand-color-2: var(--blue-color-2);
/* ...其他业务色映射 */
}
/* 暗黑主题映射 src/styles/themes/dark-theme.scss */
:root[data-theme="dark"] {
--hx-brand-color-1: var(--blue-color-4);
--hx-brand-color-2: var(--blue-color-5);
/* ...其他暗黑主题业务色映射 */
}在组件中使用映射层的业务名称,而不直接使用基础层变量
.button {
background-color: var(--hx-brand-color-3);
color: var(--hx-gray-color-1);
/* ...其他样式 */
}# 安装Vue CLI(如果尚未安装)
npm install -g @vue/cli
# 创建新项目,指定Vue2版本
vue create ui-project --preset vue2
# 进入项目目录
cd ui-project# 安装SCSS支持
npm install sass@1.32.13 sass-loader@10.4.1 -D
# 安装Tailwind CSS(可选)
npm install tailwindcss@3.3.3 postcss@8.4.24 autoprefixer@10.4.14 postcss-loader@4.3.0 -D
# 如果需要Element UI(可选)
npm install element-ui@2.15.14mkdir -p src/styles/tokens
mkdir -p src/styles/themes-
基础层文件:
src/styles/tokens/base-colors.scsssrc/styles/tokens/base-fonts.scsssrc/styles/tokens/base-spacing.scsssrc/styles/tokens/base-radius.scss
-
映射层文件:
src/styles/themes/default-theme.scsssrc/styles/themes/dark-theme.scss
-
主样式文件:
src/styles/index.scss
// src/utils/themeUtils.js
// 切换主题
export const switchTheme = (theme) => {
document.documentElement.setAttribute('data-theme', theme);
localStorage.setItem('app-theme', theme);
};
// 初始化主题
export const initTheme = () => {
const savedTheme = localStorage.getItem('app-theme');
if (savedTheme) {
switchTheme(savedTheme);
} else {
const prefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches;
switchTheme(prefersDark ? 'dark' : 'default');
}
};// src/utils/styleUtils.js
export const getCSSVar = (varName) => {
const name = varName.startsWith('--') ? varName : `--${varName}`;
return getComputedStyle(document.documentElement).getPropertyValue(name).trim();
};外部组件可以通过以下方式应用自定义UI样式:
/* src/styles/element-override.scss */
.el-button--primary {
background-color: var(--hx-brand-color-3);
border-color: var(--hx-brand-color-3);
&:hover, &:focus {
background-color: var(--hx-brand-color-2);
border-color: var(--hx-brand-color-2);
}
/* ...其他状态 */
}<style lang="scss" scoped>
.custom-form {
/* 使用 ::v-deep 深度选择器覆盖子组件样式 */
::v-deep .el-input__inner {
height: calc(var(--hx-comp-size-m) - 2px);
border-radius: var(--hx-radius-default);
/* ...其他样式 */
}
}
</style><!-- src/components/ui/StyledButton.vue -->
<template>
<el-button
:type="type"
:size="size"
:disabled="disabled"
v-bind="$attrs"
v-on="$listeners"
>
<slot></slot>
</el-button>
</template>
<script>
export default {
name: 'StyledButton',
inheritAttrs: false,
props: {
type: String,
size: String,
disabled: Boolean
}
}
</script>
<style lang="scss" scoped>
::v-deep .el-button {
&.el-button--primary {
background-color: var(--hx-brand-color-3);
/* ...其他样式 */
}
/* ...其他类型的按钮 */
}
</style>// src/utils/chartTheme.js
import { getCSSVar } from './styleUtils';
// 创建与UI系统匹配的ECharts主题
export const createChartTheme = () => {
return {
// 颜色系列
color: [
getCSSVar('hx-brand-color-3'),
getCSSVar('hx-sec-brand-color-3'),
getCSSVar('hx-success-color-3'),
getCSSVar('hx-warning-color-3'),
getCSSVar('hx-danger-color-3')
],
// 背景色
backgroundColor: getCSSVar('hx-bg-color-container'),
// 全局文字样式
textStyle: {
color: getCSSVar('hx-font-color-1')
},
// 坐标轴
xAxis: {
axisLine: {
lineStyle: {
color: getCSSVar('hx-border-level-2-color')
}
},
axisLabel: {
color: getCSSVar('hx-font-color-2')
}
},
yAxis: {
axisLine: {
lineStyle: {
color: getCSSVar('hx-border-level-2-color')
}
},
axisLabel: {
color: getCSSVar('hx-font-color-2')
}
},
// 提示框
tooltip: {
backgroundColor: getCSSVar('hx-bg-color-container'),
borderColor: getCSSVar('hx-border-level-3-color'),
textStyle: {
color: getCSSVar('hx-font-color-1')
}
}
// ...其他样式配置
};
};
// 注册主题到ECharts
export const registerTheme = (echarts) => {
echarts.registerTheme('hxTheme', createChartTheme());
};<!-- src/components/charts/BaseChart.vue -->
<template>
<div class="chart-container" :style="containerStyle">
<div class="chart-title" v-if="title">{{ title }}</div>
<div ref="chartRef" class="chart-instance" :style="chartStyle"></div>
</div>
</template>
<script>
import * as echarts from 'echarts';
import { createChartTheme, registerTheme } from '@/utils/chartTheme';
export default {
name: 'BaseChart',
props: {
title: String,
options: Object,
height: {
type: String,
default: '400px'
},
width: {
type: String,
default: '100%'
}
},
data() {
return {
chart: null
};
},
mounted() {
this.initChart();
// 监听主题变化
this.observer = new MutationObserver(() => {
this.updateChartTheme();
});
this.observer.observe(document.documentElement, {
attributes: true,
attributeFilter: ['data-theme']
});
},
methods: {
initChart() {
// 注册主题到ECharts
registerTheme(echarts);
// 初始化图表实例
this.chart = echarts.init(this.$refs.chartRef, 'hxTheme');
if (this.options) {
this.chart.setOption(this.options);
}
this.$emit('ready', this.chart);
},
updateChartTheme() {
if (!this.chart) return;
const theme = createChartTheme();
const themeOptions = {
color: theme.color,
backgroundColor: theme.backgroundColor,
textStyle: theme.textStyle
};
this.chart.setOption(themeOptions);
}
},
watch: {
options: {
handler(newOptions) {
if (this.chart && newOptions) {
this.chart.setOption(newOptions, true);
}
},
deep: true
}
},
beforeDestroy() {
if (this.chart) {
this.chart.dispose();
}
if (this.observer) {
this.observer.disconnect();
}
}
}
</script><template>
<base-chart
title="月度销售数据"
:options="barOptions"
height="350px"
/>
</template>
<script>
import BaseChart from '@/components/charts/BaseChart.vue';
import { getCSSVar } from '@/utils/styleUtils';
export default {
components: {
BaseChart
},
data() {
return {
barOptions: {
tooltip: {
trigger: 'axis'
},
xAxis: {
type: 'category',
data: ['一月', '二月', '三月', '四月', '五月', '六月']
},
yAxis: {
type: 'value'
},
series: [
{
name: '销售额',
type: 'bar',
data: [120, 132, 101, 134, 90, 230],
// 使用业务颜色
itemStyle: {
color: getCSSVar('hx-brand-color-3')
}
}
]
}
};
}
}
</script>graph TD
A[基础层 Base Layer] --> B[映射层 Mapping Layer]
B --> C1[Tailwind 工具类]
B --> C2[CSS/SCSS 直接使用]
subgraph 基础层
A1[base-colors.scss] --> A
A2[base-fonts.scss] --> A
A3[base-spacing.scss] --> A
A4[base-radius.scss] --> A
end
subgraph 映射层
B1[default-theme.scss] --> B
B2[dark-theme.scss] --> B
end
subgraph 应用层
C1 --> D1[class='text-brand bg-brand-light p-2']
C2 --> D2["custom-class: color: var(--hx-brand-color-3)"]
end
- 基础变量定义 → 主题映射 → 实际使用
graph LR
A[基础变量] --> B[主题映射] --> C[实际使用]
subgraph 基础变量
A1[--blue-color-1: #E3F4FF]
end
subgraph 主题映射
B1[--hx-brand-color-1: var<br>--blue-color-1]
end
subgraph 实际使用
C1[Tailwind类:<br>bg-brand-light]
C2[CSS变量:<br>var--hx-brand-color-1]
end
- Tailwind工具类方式:
<!-- 使用Tailwind工具类 -->
<div class="bg-brand text-white p-2 rounded-default">
按钮
</div>- CSS变量方式:
/* 使用CSS变量 */
.custom-button {
background-color: var(--hx-brand-color-3);
color: var(--hx-gray-color-1);
padding: var(--hx-size-2);
border-radius: var(--hx-radius-default);
}graph LR
A[基础色值] --> B[主题变量] --> C[使用方式]
subgraph 基础色值
A1["--blue-color-1<br>#E3F4FF"]
end
subgraph 主题变量
B1["--hx-brand-color-1"]
end
subgraph 使用方式
C1["Tailwind:<br>bg-brand-light"]
C2["CSS变量:<br>var(--hx-brand-color-1)"]
end
graph TD
A[默认主题] --> B{切换主题}
B -->|切换到暗色| C[暗色主题]
B -->|切换到默认| D[默认主题]
subgraph 主题切换代码
E["// 切换到暗色主题<br>document.documentElement<br>.setAttribute('data-theme', 'dark')"]
F["// 切换到默认主题<br>document.documentElement<br>.removeAttribute('data-theme')"]
end
- 纯Tailwind工具类(推荐用于快速开发):
<button class="bg-brand text-white p-2 rounded-default hover:bg-brand-dark">
提交
</button>- 纯CSS变量(推荐用于自定义组件):
.custom-component {
background: var(--hx-brand-color-3);
color: var(--hx-gray-color-1);
padding: var(--hx-size-2);
border-radius: var(--hx-radius-default);
&:hover {
background: var(--hx-brand-color-dark);
}
}- 混合使用(推荐用于复杂组件):
<div class="bg-brand p-2"> <!-- Tailwind工具类 -->
<div class="custom-component"> <!-- 自定义类 -->
内容
</div>
</div>为确保开发过程中正确使用设计令牌(Design Tokens),请遵循以下规范:
UI Token遵循以下命名模式: --[前缀]-[类别]-[属性]-[状态/变体]
例如:
--hx-brand-color-3: 品牌色第3级--hx-font-size-body-medium: 正文中号字体大小--hx-border-level-1-color: 一级边框颜色
-
基础令牌(Foundation Tokens)
- 位于
src/styles/tokens/目录 - 直接表示原始值,不用于直接调用
- 例:
--blue-color-1,--size-1
- 位于
-
主题令牌(Theme Tokens)
- 位于
src/styles/themes/目录 - 映射基础令牌到具体业务语义
- 例:
--hx-brand-color-1,--hx-text-color-primary
- 位于
-
组件令牌(Component Tokens)
- 在组件中使用的特定变量
- 基于主题令牌构建
- 例:
.button { background: var(--hx-brand-color-3); }
-
颜色令牌
- 品牌色:
--hx-brand-color-[级别] - 功能色:
--hx-success-color-[级别],--hx-warning-color-[级别],--hx-danger-color-[级别] - 中性色:
--hx-gray-color-[级别] - 文本色:
--hx-text-color-[级别] - 边框色:
--hx-border-level-[级别]-color - 背景色:
--hx-bg-color-[类型]
- 品牌色:
-
尺寸令牌
- 基础尺寸:
--hx-size-[级别] - 组件尺寸:
--hx-comp-size-[级别] - 内边距:
--hx-comp-padding[方向]-[级别] - 外边距:
--hx-comp-margin-[级别]
- 基础尺寸:
-
字体令牌
- 字体族:
--hx-font-family - 字体大小:
--hx-font-size-[类型]-[级别] - 行高:
--hx-line-height-[类型]-[级别] - 组合字体:
--hx-font-[类型]-[级别]
- 字体族:
-
圆角令牌
- 圆角:
--hx-radius-[级别]
- 圆角:
对UI元素的不同状态使用以下后缀:
- 默认:
-normal或无后缀 - 悬浮:
-hover - 聚焦:
-focus - 激活:
-active - 禁用:
-disabled或-disable
例如:
.button {
background: var(--hx-brand-color-3); // 默认状态
&:hover {
background: var(--hx-brand-color-hover); // 悬浮状态
}
&:disabled {
background: var(--hx-brand-color-disabled); // 禁用状态
}
}-
始终使用主题令牌,不要直接使用基础令牌
- ✅
var(--hx-brand-color-3) - ❌
var(--blue-color-3)
- ✅
-
为新组件使用最接近的语义化令牌
- ✅
var(--hx-warning-color-3)表示警告状态 - ❌ 直接使用
#FFB200色值
- ✅
-
扩展令牌时保持命名一致性
- 遵循已有的命名规则和格式
- 新令牌应遵循类似层次结构
-
使用注释清晰标记令牌用途
/* 主按钮 */ .primary-button { /* 使用品牌主色作为背景 */ background-color: var(--hx-brand-color-3); }
-
考虑所有主题的适配
- 确保新添加的组件在所有主题下都有对应的样式表现
- 测试组件在默认主题和暗黑主题下的表现
当遇到样式问题时,可以使用以下方法调试:
// 在控制台查看变量值
getComputedStyle(document.documentElement).getPropertyValue('--hx-brand-color-3').trim()
// 或使用封装的工具函数
function getCSSVar(name) {
const varName = name.startsWith('--') ? name : `--${name}`;
return getComputedStyle(document.documentElement).getPropertyValue(varName).trim();
}- 始终使用业务层变量,不要直接使用基础层变量:
- ✅
var(--hx-brand-color-3) - ❌
var(--blue-color-3)
- ✅
每个组件应该定义以下状态的样式:
- 正常状态
- 悬浮状态
:hover - 聚焦状态
:focus - 激活状态
:active - 禁用状态
.is-disabled或[disabled]
- 引入基础图表组件
- 准备图表配置
- 使用UI变量定义颜色和样式
- 监听图表事件进行交互
遵循已有的样式风格和命名规范,使用UI变量定义样式