模块一:专业基石 - 构筑自动化与企业级的开发环境

模块一:专业基石 - 构筑自动化与企业级的开发环境

本模块任务清单

场景一:技术启动会
项目启动第一天

本模块将从零开始,构建一个功能完备、高度规范化的 Vue 3 企业级开发环境。我们将集成并深度配置所有核心依赖,建立一套全自动化的代码质量保障与提交体系。

通过以下 9 个核心任务,您将掌握构建企业级 Vue 3 开发环境的完整流程:

任务模块任务名称描述
项目初始化项目模板创建使用 pnpmVite 创建纯净的 Vue + TypeScript 项目模板。
代码规范体系ESLint & Prettier 集成集成 ESLintPrettier,采用 eslint.config.js 范式,统一代码质量与风格。
路径别名配置开发效率基础设施配置路径别名,为后续章节的模块引用提供基础支持。
UI 组件库集成Element Plus 按需导入安装 Element Plus,利用 unplugin-vue-components 实现组件按需自动导入。
国际化配置Element Plus 中文化将 Element Plus 默认语言从英语切换为简体中文。
SCSS 企业级架构样式系统构建集成 SCSS,建立企业级的样式架构和 Element Plus 主题定制方案。
核心服务集成基础服务安装与配置完成 TanStack Query, Pinia, Vue RouterAxios 的基础安装与初始化配置。
API 自动导入开发效率优化配置 API 自动导入,消除重复的 import 语句,提升编码效率。
自动化提交流程Husky & lint-staged配置 Huskylint-staged,在 Git 提交流程中强制执行代码检查。

1.1 项目初始化

我们从最基础的一步开始:创建一个全新的项目。

当前任务: 1.1 - 项目初始化

任务目标: 使用业界推荐的包管理器 pnpm 和构建工具 Vite,快速生成一个标准的 Vue 3 + TypeScript 项目骨架。

1.1.1 设计思路与技术选型

在包管理器方面,我们优先选择 pnpm,而非 npmyarnpnpm 的核心优势体现在以下几个方面:

  • 极致的磁盘空间效率: pnpm 采用内容寻址和硬链接技术,确保相同版本的依赖在磁盘上只存储一份,极大地节省了存储空间。
  • 闪电般的安装速度: 凭借对本地缓存的优先利用,pnpm 在依赖安装速度上显著优于其竞争者。
  • 严格的依赖管理: pnpm 的非扁平化 node_modules 结构从根本上解决了 “幽灵依赖” 问题,从而增强了项目的稳定性和可维护性。

在构建工具方面,Vite 是我们现代 Vue 项目开发的标准配置。其基于原生 ES 模块的开发服务器,提供了无与伦比的冷启动速度和热模块更新(HMR)性能,极大地提升了开发体验。

1.1.2 命令行操作

首先,请确保您的开发环境中已全局安装 pnpm。如果尚未安装,可以通过 npm 执行以下命令进行安装:

1
npm install -g pnpm

接下来,打开终端并导航至您的工作目录。执行以下命令来启动项目创建流程:

1
pnpm create vite

Vite 的脚手架工具将引导您完成一系列项目配置选择。请按照以下配置项进行选择:

  • Project name: vue3-webShop
  • Select a framework: Vue
  • Select a variant: TypeScript

完成上述选择后,Vite 将在 /path/to/your/workspace/vue3-webShop 路径下完成项目骨架的搭建。随后,按照提示进入项目目录并安装所有依赖:

1
2
cd vue3-webShop
pnpm install

1.1.3 成果预览:初始项目结构

执行完以上命令后,您将获得一个纯净且标准的 Vite + Vue 3 项目结构,其目录概览如下:

1
2
3
4
5
6
7
8
9
10
11
12
# vue3-webShop/
├── public/
├── src/
│ ├── assets/
│ ├── components/
│ ├── App.vue
│ └── main.ts
├── .gitignore
├── index.html
├── package.json
├── tsconfig.json
└── vite.config.ts

至此,项目的 “毛坯房” 已经搭建完毕。这是一个坚实且干净的起点,我们将在其之上,逐步添砖加瓦,构筑我们的应用大厦。


1.2 代码规范体系

在团队协作中,代码不仅是实现功能的工具,更是沟通的媒介。一套统一、自动化的代码规范体系,是保障项目长期可维护性、提升团队协作效率的基石。

当前任务: 1.2 - 代码规范体系
任务目标: 为项目集成 ESLint (代码质量检查) 和 Prettier (代码风格格式化),并采用最新的 eslint.config.js 范式,建立一套专业的、自动化的代码规范标准。

1.2.1 设计思路:质量与风格的分离

在配置之前,我们必须理解两个核心工具的职责边界:

  • ESLint (代码质量卫士): 它的核心职责是 发现代码中的潜在错误和不合理的写法。例如,是否存在未被使用的变量、是否在 switch 语句中缺少 break 等。它关心的是代码的 正确性健壮性
  • Prettier (代码风格造型师): 它的职责非常纯粹,即 统一代码的格式。例如,是使用单引号还是双引号、行尾是否需要分号、代码行的最大宽度等。它只关心代码的 外观,不关心其逻辑。

我们的策略是让它们各司其职,并通过配置让它们完美协作:ESLint 负责修复代码质量问题,Prettier 负责最终的格式化,且 Prettier 的优先级更高,以避免两者在风格规则上的冲突。

1.2.2 核心依赖安装

现在,我们将为项目安装所有必需的 “纪律委员”。请在项目根目录下执行以下命令:

1
pnpm add -D @eslint/js eslint eslint-config-prettier eslint-plugin-vue globals prettier typescript-eslint

依赖解读:

  • eslint & @eslint/js: ESLint 本体及其官方核心规则集。
  • typescript-eslint: 用于让 ESLint 理解和校验 TypeScript 语法的核心插件。
  • eslint-plugin-vue: 专为 .vue 单文件组件量身打造的规则插件。
  • eslint-config-prettier: 关键!用于关闭所有与 Prettier 冲突的 ESLint 规则,确保 Prettier 拥有最终的格式化决定权。
  • prettier: Prettier 本体。
  • globals: 用于预设浏览器、Node.js 等环境的全局变量,避免 ESLint 报错。

1.2.3 现代化 ESLint 配置

ESLint 的配置已全面转向基于 ES Module 的 eslint.config.js 文件。这种新范式提供了前所未有的灵活性。

现在,我们在项目根目录创建 eslint.config.js 文件,并填入以下内容。让我们来逐块解析它的结构:

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
// eslint.config.js
import globals from "globals";
import eslintJs from "@eslint/js";
import tseslint from "typescript-eslint";
import eslintPluginVue from "eslint-plugin-vue";
import eslintConfigPrettier from "eslint-config-prettier";

export default tseslint.config(
// 全局忽略配置
{
ignores: ["dist", "node_modules", "*.config.js", "public", ".DS_Store"],
},
// 基础配置,应用于所有文件
{
languageOptions: {
globals: {
...globals.browser,
...globals.es2021,
},
},
},
// ESLint 官方推荐规则
eslintJs.configs.recommended,
// TypeScript ESLint 推荐规则
...tseslint.configs.recommended,
// Vue 插件推荐规则 (针对 Vue 3)
...eslintPluginVue.configs["flat/recommended"],
// Prettier 配置,必须放在最后
eslintConfigPrettier,
// 自定义规则配置
{
files: ["**/*.vue"],
languageOptions: {
parserOptions: {
parser: tseslint.parser,
},
},
rules: {
"vue/multi-word-component-names": "off",
"vue/html-self-closing": [
"error",
{
html: {
void: "always",
normal: "never",
component: "always",
},
},
],
"no-console": process.env.NODE_ENV === "production" ? "warn" : "off",
"no-debugger": process.env.NODE_ENV === "production" ? "warn" : "off",
},
}
);

配置解读:

  1. ignores: 全局忽略配置。我们明确告诉 ESLint 不要检查构建产物、依赖、配置文件等,以提升检查效率并避免误报。
  2. languageOptions.globals: 全局变量配置。我们引入了 globals 包中预设的浏览器和 ES2021 环境的全局变量,这样在代码中使用 windowPromise 等就不会被 ESLint 判为未定义。
  3. eslintJs.configs.recommended: 启用 ESLint 官方的核心推荐规则。
  4. ...tseslint.configs.recommended: 启用 typescript-eslint 的推荐规则集,用于检查 TypeScript 代码。
  5. ...eslintPluginVue.configs["flat/recommended"]: 启用 eslint-plugin-vue 针对 Vue 3 的推荐规则集。
  6. eslintConfigPrettier: 关键步骤。此配置会禁用所有与 Prettier 格式化功能冲突的 ESLint 规则。它必须放在配置数组的靠后位置,以确保它能覆盖之前所有规则集中的样式规则。
  7. 自定义规则: 这是我们项目的专属规则区。
    • files: ["**/*.vue"] 指定了这些规则仅对 .vue 文件生效。
    • parserOptions: 为 Vue 文件的 <script> 部分指定 TypeScript 解析器。
    • rules:
      • 'vue/multi-word-component-names': 'off': 关闭了组件名必须为多词的规则,在某些场景下(如根组件 App.vue)可以更灵活。
      • 'vue/html-self-closing': 配置了 HTML 元素的自闭合风格,增强代码一致性。
      • 'no-console', 'no-debugger': 设置了在生产环境下(process.env.NODE_ENV === 'production')禁止使用 consoledebugger,这是非常重要的生产环境最佳实践。

1.3 路径别名配置

为了在后续开发中方便地引用 src 目录下的文件,避免使用复杂的相对路径,我们需要先设置路径别名。这是后续章节正常使用 @/ 前缀的基础配置。

当前任务: 1.3 - 路径别名配置
任务目标: 配置 @ 作为 src 目录的别名,为项目建立简洁的模块引用方式。

1.3.1 设计思路

在大型项目中,文件结构往往很深,使用相对路径会导致这样的引用:

1
2
import { apiCall } from "../../../utils/http";
import UserComponent from "../../../components/User.vue";

通过配置路径别名,我们可以将其简化为:

1
2
import { apiCall } from "@/utils/http";
import UserComponent from "@/components/User.vue";

这不仅让代码更简洁,还避免了在移动文件时需要修改引用路径的问题。

1.3.2 Vite 配置

在项目根目录的 vite.config.ts 文件中添加路径别名配置:

1
2
3
4
5
6
7
8
9
10
11
12
13
// vite.config.ts
import { fileURLToPath, URL } from "node:url";
import { defineConfig } from "vite";
import vue from "@vitejs/plugin-vue";

export default defineConfig({
plugins: [vue()],
resolve: {
alias: {
"@": fileURLToPath(new URL("./src", import.meta.url)),
},
},
});

1.3.3 TypeScript 支持

为了让 TypeScript 理解这个路径别名,我们需要更新 TypeScript 配置。

方法一:如果您的项目使用单一 tsconfig.json

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
{
"compilerOptions": {
"target": "ES2020",
"lib": ["ES2020", "DOM", "DOM.Iterable"],
"module": "ESNext",
"moduleResolution": "bundler",
"strict": true,
"jsx": "preserve",
"esModuleInterop": true,
"allowSyntheticDefaultImports": true,
"forceConsistentCasingInFileNames": true,
"skipLibCheck": true,
"baseUrl": ".",
"paths": {
"@/*": ["src/*"]
}
},
"include": ["src/**/*.ts", "src/**/*.d.ts", "src/**/*.tsx", "src/**/*.vue"]
}

方法二:如果您的项目使用 tsconfig.app.json (推荐)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// tsconfig.app.json
{
"extends": "@vue/tsconfig/tsconfig.dom.json",
"compilerOptions": {
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
"strict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noFallthroughCasesInSwitch": true,

/* 路径别名配置 */
"baseUrl": ".",
"paths": {
"@/*": ["src/*"]
}
},
"include": ["src/**/*.ts", "src/**/*.d.ts", "src/**/*.tsx", "src/**/*.vue"]
}

修改 TypeScript 配置文件后,请务必通过 VS Code 命令面板 (Ctrl+Shift+P) 执行 TypeScript: Restart TS Server 命令来重启 TypeScript 服务,以确保路径提示生效。

1.3.4 功能测试

配置完成后,在任意 Vue 组件中尝试使用 @/ 前缀:

1
2
3
4
5
6
7
8
9
10
<script setup lang="ts">
// 测试路径别名是否生效
import { ref } from "vue";
// 假设后续章节会创建这个文件
// import { someUtil } from '@/utils/helpers'
</script>

<template>
<div>路径别名配置完成</div>
</template>

如果 VS Code 能够正确提供路径提示和自动补全,说明配置成功。


1.4 UI 组件库与图标库集成

为项目引入一套成熟的 UI 组件库,是现代前端开发的标准实践。它能极大提升开发效率,保证 UI 的一致性和可访问性,让我们能专注于业务逻辑的实现。

当前任务: 1.4 - UI 组件库与图标库集成
任务目标: 为项目安装 Element Plus 及其官方图标库,并利用 unplugin-vue-componentsunplugin-auto-importunplugin-icons 插件,实现组件 和图标 的按需自动导入。

1.4.1 设计思路:按需自动导入

在项目中引入 Element Plus 有两种方式:全局引入和按需导入。

  • 全局引入: 简单直接,但会将所有组件和图标打包,导致最终构建产物体积过大。
  • 按需导入: 只打包代码中实际使用到的组件,是生产环境的最佳实践。

为了实现优雅的按需导入,避免在每个文件中手动 import { ElButton } from 'element-plus'import { Edit } from '@element-plus/icons-vue',我们借助 unplugin 系列插件来自动化这一过程。

1.4.2 核心依赖安装

我们将安装 element-plus 本体、其官方图标库,以及三个实现自动导入的 unplugin 插件。请在项目根目录下执行以下命令:

1
2
3
4
5
# 1. 安装 element-plus 和图标库
pnpm add element-plus @element-plus/icons-vue

# 2. 安装 unplugin 系列插件
pnpm add -D unplugin-vue-components unplugin-auto-import unplugin-icons @iconify-json/ep

依赖解读:

包名核心职责
element-plusElement Plus 组件库本体。
@element-plus/icons-vueElement Plus 的官方图标库,其中每个图标都是一个独立的 Vue 组件。
unplugin-auto-import自动导入 API,如 ElMessage 等 JS 调用方法。
unplugin-vue-components自动导入组件,扫描模板中的标签(如 <el-button>)并自动导入。
unplugin-icons一个强大的图标插件,可以与 unplugin-vue-components 协同工作,实现图标的自动导入。
@iconify-json/epElement Plus 图标的 Iconify 数据包,为 unplugin-icons 提供图标数据。

1.4.3 Vite 配置文件 (vite.config.ts)

安装完依赖后,我们需要在 vite.config.ts 中启用并配置这些插件。

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
// vite.config.ts
import { fileURLToPath, URL } from "node:url";
import { defineConfig } from "vite";
import vue from "@vitejs/plugin-vue";

// 1. 引入所需插件
import AutoImport from "unplugin-auto-import/vite";
import Components from "unplugin-vue-components/vite";
import { ElementPlusResolver } from "unplugin-vue-components/resolvers";

// 引入 unplugin-icons
import Icons from "unplugin-icons/vite";
import IconsResolver from "unplugin-icons/resolver";

export default defineConfig({
plugins: [
vue(),
// 2. 配置 unplugin-auto-import
AutoImport({
resolvers: [ElementPlusResolver()],
}),
// 3. 配置 unplugin-vue-components
Components({
resolvers: [
// 自动导入 Element Plus 组件
ElementPlusResolver({ importStyle: "sass" }),
// 自动导入 Element Plus 图标
// 我们约定图标组件的使用方式是 <i-ep-iconName />
IconsResolver({
prefix: "i", // 默认为 i, 可不写
enabledCollections: ["ep"], // ep 是 element-plus 的图标库
}),
],
}),
// 4. 配置 unplugin-icons
Icons({
autoInstall: true,
}),
],
resolve: {
alias: {
"@": fileURLToPath(new URL("./src", import.meta.url)),
},
},
});

配置解读:

  • IconsResolver: 这是专门用于解析图标的解析器。
    • enabledCollections: ['ep']: 告诉解析器只启用 Element Plus 的图标集 (ep 是其简称)。
  • 使用约定: 我们建立了一个清晰的图标使用约定:所有以 <i-ep- 开头的标签(例如 <i-ep-edit />),都会被自动识别为 Element Plus 的图标并导入。

1.4.4 TypeScript 类型支持 (Volar)

为了让 TypeScript 和 Volar 能够识别自动导入的组件和图标,我们需要确保 tsconfig.jsoninclude 配置包含了插件自动生成的类型声明文件。

1
2
3
4
5
6
7
8
9
10
11
12
13
// tsconfig.json
{
// ...
"include": [
"src/**/*.ts",
"src/**/*.d.ts",
"src/**/*.tsx",
"src/**/*.vue",
"auto-imports.d.ts",
"components.d.ts"
]
// ...
}

修改配置后,你可能需要重启 VS Code 或执行 TypeScript: Restart TS Server 命令来让类型提示生效。

1.4.5 功能测试

配置完成后,我们来验证组件和图标是否都已成功集成。打开 src/App.vue 文件,替换为以下代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<script setup lang="ts"></script>

<template>
<el-button type="primary">我是按钮</el-button>

<el-button type="success">
<i-ep-edit />
编辑按钮
</el-button>

<div>
<i-ep-success color="green" style="font-size: 2em" />
<i-ep-warning color="orange" style="font-size: 2em" />
<i-ep-circle-close color="red" style="font-size: 2em" />
</div>
</template>

现在,启动开发服务器 (pnpm run dev)。如果你能看到按钮和图标都正常显示,那么恭喜你,自动导入已成功配置!

1.4.6 深度解析:图标的两种用法 (手动 vs 自动)

你可能会有疑问:为什么我们不使用官方文档中常见的 import { Edit } from '@element-plus/icons-vue' 方式,而是用 <i-ep-edit />?这是一个非常好的问题,理解它有助于你掌握现代前端的工程化思想。

开发者对话
配置完成后

等等,我平时用图标都是 import { Phone } from '@element-plus/icons-vue',然后在模板里用 :icon="Phone"。你教的 <i-ep-phone /> 是什么新语法?

架构师

问得好!你说的 手动导入 是 Vue 最经典、最直观的用法。而我们配置的 自动导入,是利用工具链提升效率的 工程化最佳实践。两者都能用,但后者能让你的代码更简洁。

有什么区别?

架构师

手动导入,你是在 运行时 告诉 Vue,“Phone 这个变量是一个组件”。而自动导入,是插件在 编译时 就帮你把 <i-ep-phone /> 这个 “暗号” 直接替换成了真正的组件和导入语句。

我明白了!所以自动导入省去了我在 <script> 里写一大堆 import 的麻烦,想用哪个图标,直接在模板里写 “暗号” 就行了。

架构师

完全正确!这就是现代前端工程化的魅力所在——把重复的、机械的工作交给工具,让开发者专注于创造。

方法 A: 手动导入 (传统方式)

这是 Element Plus 官方文档中最常见的用法,依赖关系清晰。

  • 代码示例:
    1
    2
    3
    4
    5
    6
    <script setup lang="ts">
    import { Phone } from "@element-plus/icons-vue";
    </script>
    <template>
    <el-link :icon="Phone">在线客服</el-link>
    </template>
  • 优点: 依赖关系明确,无需额外配置。
  • 缺点: 当使用大量图标时,<script> 部分会变得非常冗长。

方法 B: 自动导入 (工程化实践)

这是我们通过插件配置实现的用法,开发体验极致高效。

  • 代码示例:
    1
    2
    3
    4
    <template>
    <!-- 无需在 script 中做任何事 -->
    <el-button type="success"> <i-ep-edit /> 编辑按钮 </el-button>
    </template>
  • 优点: <script> 保持干净,开发效率极高,代码更整洁。
  • 缺点: 需要前期配置,对于不熟悉配置的人来说有 “魔法” 成分。
特性手动导入 (传统方式)自动导入 (工程化实践)
核心思想运行时,开发者手动管理依赖编译时,插件自动注入依赖
代码整洁度<script> 部分较长,依赖明确<script> 部分干净,模板即声明
开发效率较低,需要手动导入极高,开箱即用
推荐实践偶尔需要将图标作为变量传递时使用现代前端工程化的首选方案,适用于绝大部分场景

结论:
在本教程中,我们将 优先使用自动导入 的方式,因为它极大地提升了开发体验,是构建大型企业级项目的首选。理解这两种方式的差异,将让你对现代前端工具链有更深刻的认识。


1.5 国际化 (i18n) 配置

Element Plus 组件库默认的语言是英语。对于面向中文用户的项目,我们需要进行国际化配置,将提示文字、日期格式等转换为中文,以提供符合用户习惯的体验。

当前任务: 1.5 - 国际化 (i18n) 配置
任务目标: 将 Element Plus 组件的默认语言从英语(en)切换为简体中文(zh-cn)。

1.5.1 设计思路

Element Plus 提供了两种主流的国际化配置方案:

  1. 全局配置: 在应用入口文件 (main.ts) 中,通过 app.use 传入全局配置对象。这是最常用、最直接的方式,一次配置,全局生效。
  2. ElConfigProvider 组件: 使用 ElConfigProvider 组件包裹根组件或部分组件树,可以实现全局或局部的语言配置。

对于我们整个应用都需要使用中文的场景,全局配置 是最佳选择。

1.5.2 全局配置实现

我们将修改 main.ts 文件,导入 Element Plus 的中文语言包并进行全局注册。

现在,让我们整合这段逻辑到 main.ts 文件中。如果架构师的 main.ts 中已经有 app.use(ElementPlus),请将其替换为带有配置对象的版本。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// src/main.ts

import { createApp } from "vue";
import App from "./App.vue";

import ElementPlus from "element-plus";
// 1. 导入 Element Plus 的 CSS 样式文件
import "element-plus/dist/index.css";
// 2. 导入 Element Plus 的中文语言包
// 注意:新版本 Element Plus 的语言包路径可能有所变化,请以实际为准
// .mjs 后缀表示这是一个 ES Module
import zhCn from "element-plus/es/locale/lang/zh-cn";

const app = createApp(App);

// 3. 全局注册 Element Plus 并配置国际化
app.use(ElementPlus, {
locale: zhCn,
});

app.mount("#app");

配置解读:

  • 我们从 element-plus/dist/locale/ 目录下导入了 zh-cn.mjs,这是一个包含了所有组件中文翻译的对象。
  • 在调用 app.use(ElementPlus) 时,我们传入了第二个参数,一个配置对象 { locale: zhCn }。Element Plus 内部会接收这个对象,并将 zhCn 设为默认语言包。

1.5.3 功能测试

如何验证国际化配置是否生效?我们可以使用一个包含大量文本的组件,例如日期选择器 (ElDatePicker)。

修改 src/App.vue 进行测试:

1
2
3
4
5
6
7
8
9
10
11
<template>
<div class="test-container">
<el-date-picker type="date" placeholder="请选择日期" />
</div>
</template>

<style scoped>
.test-container {
padding: 20px;
}
</style>

现在,运行项目。当你点击日期选择框时,弹出的面板中的 “一月”、“周一”、“今天” 等文字如果都显示为中文,则证明我们的国际化配置已成功生效。

现在,运行项目。当你点击日期选择框时,弹出的面板中的 “一月”、“周一”、“今天” 等文字如果都显示为中文,则证明我们的国际化配置已成功生效。


1.6 SCSS 企业级架构与主题定制

在企业级项目中,样式的可维护性、可扩展性和一致性至关重要。本节,我们将摒弃零散的样式文件,构建一套专业、分层的 SCSS 架构。我们将学习如何集成并定制 Element Plus,同时建立一套包含全局变量、基础规范、混入(Mixins)的完整体系,并通过一个统一的入口文件进行管理,最终由 Vite 实现高效的全局注入。

当前任务: 1.6 - 构建企业级 SCSS 架构
任务目标: 建立一套专业的、可扩展的 SCSS 文件结构。通过 Vite additionalData 全局注入编译时工具(变量、混入),并通过 main.ts 导入唯一的全局样式文件,实现最高效的样式管理方案。

1.6.1 设计思路:全局样式与编译工具的分离

一个顶级的样式架构必须清晰地分离两个概念:

  1. 全局样式表 (Global Stylesheet): 这是应用的基础外观,包含 CSS 重置、基础排版 (body, a 标签等) 和 Element Plus 的定制化样式。这些样式应该被编译成一个单一的 CSS 文件,并在应用入口 (main.ts) 加载且仅加载一次
  2. 编译时工具 (Compile-Time Tools): 这是我们在编写组件独有样式时需要的设计令牌和代码片段,如 $GLColor 变量和 flex-center 混入。这些工具本身不产生 CSS,它们需要在 每个组件的 <style> 块中都可用,以便我们遵循设计规范。

我们将通过 main.scss 文件来组织 全局样式表,并通过 Vite 的 additionalData 配置来全局提供 编译时工具

1.6.2 依赖安装与文件结构创建

  1. 安装 SCSS 编译器与重置库:

    1
    pnpm add -D sass scss-reset
  2. 创建全新的样式文件结构:
    src/ 目录下创建 styles 文件夹,并建立以下专业结构:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    src/
    └── styles/
    ├── abstracts/
    │ ├── _variables.scss # 全局变量
    │ ├── _mixins.scss # 全局混入
    │ └── _utilities.scss # 工具类
    ├── base/
    │ └── _index.scss # 基础样式规范
    ├── element/
    │ └── _index.scss # Element Plus 变量覆盖
    └── main.scss # 唯一的全局样式总入口

1.6.3 编写抽象层 (Abstracts)

这是我们的 “工具箱”,包含所有不直接输出 CSS 的变量和混入。

1. _variables.scss

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

// 品牌色
$GLColor: #0155b2;

// 功能色
$sucColor: #1dc779;
$warnColor: #ffb302;
$priceColor: #cf4444; // 也用于危险/错误状态
$helpColor: #e26237;

// 中性色
$textColor: #333;
$textColor-secondary: #666;
$borderColor: #e4e4e4;
$bgColor: #f5f5f5;

// 其他
$borderRadius: 4px;
$transition-duration: 0.2s;

2. _mixins.scss

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

// Flexbox 快速居中
@mixin flex-center {
display: flex;
align-items: center;
justify-content: center;
}

// 文本溢出截断
@mixin truncate-text {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}

3. _utilities.scss

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// src/styles/abstracts/_utilities.scss

// 工具类混入
@mixin utilities {
.text-center {
text-align: center;
}

.text-left {
text-align: left;
}

.text-right {
text-align: right;
}
}

1.6.4 编写会输出 CSS 的样式层

这两个层级的文件会实际产出 CSS,它们需要显式地导入自己所依赖的变量。

1. base/_index.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
// src/styles/base/_index.scss

// 导入变量
@use "../abstracts/variables" as *;

// 1. 根设置: 设定 rem 计算基准和全局盒模型
html {
font-size: 62.5%; // 1rem = 10px
box-sizing: border-box;
}

*,
*::before,
*::after {
box-sizing: inherit;
}

// 2. Body 基础排版
body {
font-size: 1.6rem; // 默认 16px
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
line-height: 1.5;
color: $textColor;
background-color: $bgColor;
-webkit-font-smoothing: antialiased;
}

// 3. 基础链接规范
a {
color: $GLColor;
text-decoration: none;
transition: color $transition-duration;
&:hover {
color: lighten($GLColor, 10%);
}
}

2. element/_index.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
// src/styles/element/_index.scss

// 显式导入依赖的变量
@use "../abstracts/variables" as *;

@forward "element-plus/theme-chalk/src/common/var.scss" with (
$colors: (
"primary": (
"base": $GLColor,
),
"success": (
"base": $sucColor,
),
"warning": (
"base": $warnColor,
),
"danger": (
"base": $priceColor,
),
"error": (
"base": $priceColor,
),
)
);

1.6.5 编写全局样式入口 main.scss

此文件是 全局样式表的指挥中心,它按顺序编排所有会产出 CSS 的样式模块。

1
2
3
4
5
6
7
8
9
10
// src/styles/main.scss

// 1. 第三方库: 如样式重置库
@use "scss-reset";

// 2. 基础层: HTML 元素的全局样式
@use "base";

// 3. 框架覆盖层: 对 Element Plus 的样式定制
@use "element";

1.6.6 理解工作流:最佳注入策略

开发者日记
开发中

架构师,我们现在有了 main.scss 总入口,还有 _variables.scss 这些工具文件。我们应该如何在 Vite 中配置,才能达到最佳的性能和开发体验?

这是一个关键问题。最粗暴的方法是在 additionalData 里注入 main.scss,但这会导致 scss-resetbase 这些全局样式被重复打包进每一个组件的 CSS 中,造成代码冗余。

那该怎么办?

采用 分离策略。Vite 的 additionalData 只用来注入那些 不产出 CSS 的编译时工具——也就是我们的变量和混入。这样,每个组件的 <style> 块都能随时使用 $GLColor@include flex-center

scss-resetbase 这些真正的全局样式呢?

它们由 main.scss 统一管理,而我们只需要在项目的 JavaScript 入口,也就是 main.ts 中,import '@/styles/main.scss' 一次。这样 Vite 就会把它们打包成一个独立的、被所有页面共享的 CSS 文件。一次加载,全局生效。这就是企业级的最佳实践。

1.6.7 实施最终配置

现在,我们将整合所有配置,实施我们讨论出的最佳注入策略。

1. 更新 vite.config.ts

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
// vite.config.ts
import { fileURLToPath, URL } from "node:url";
import { defineConfig } from "vite";
import vue from "@vitejs/plugin-vue";

// 1. 引入所需插件
import AutoImport from "unplugin-auto-import/vite";
import Components from "unplugin-vue-components/vite";
import { ElementPlusResolver } from "unplugin-vue-components/resolvers";

// 引入 unplugin-icons
import Icons from "unplugin-icons/vite";
import IconsResolver from "unplugin-icons/resolver";

export default defineConfig({
plugins: [
vue(),
// 2. 配置 unplugin-auto-import
AutoImport({
resolvers: [ElementPlusResolver()],
}),
// 3. 配置 unplugin-vue-components
Components({
resolvers: [
// 1. 配置 ElementPlusResolver,指定 importStyle 为 sass
ElementPlusResolver({ importStyle: "sass" }),
// 自动导入 Element Plus 图标
IconsResolver({
prefix: "i", // 默认为 i, 可不写
enabledCollections: ["ep"], // ep 是 element-plus 的图标库
}),
],
}),
// 4. 配置 unplugin-icons
Icons({
autoInstall: true,
}),
],
resolve: {
alias: {
"@": fileURLToPath(new URL("./src", import.meta.url)),
},
},
// 2. SCSS 自动化注入配置
css: {
preprocessorOptions: {
scss: {
// 只注入变量和 mixins,避免重复导入 CSS 输出
additionalData: `
@use "@/styles/abstracts/variables" as *;
@use "@/styles/abstracts/mixins" as *;
@use "@/styles/abstracts/utilities" as *;
`,
},
},
},
});

2. 更新 main.ts

1
2
3
4
5
6
7
8
9
10
11
12
// src/main.ts
import { createApp } from "vue";
import App from "./App.vue";
import ElementPlus from "element-plus";
import zhCn from "element-plus/es/locale/lang/zh-cn";

// 核心:导入唯一的全局样式文件
import "@/styles/main.scss";

const app = createApp(App);
app.use(ElementPlus, { locale: zhCn });
app.mount("#app");

1.6.8 清理与测试

  1. 确认 main.ts: 确保 import 'element-plus/dist/index.css' 已被彻底删除,因为我们的样式体系已经完美接管。

  2. 功能测试: 重启 Vite 开发服务器。

    • 全局样式: 页面基础排版(字体、背景色等)应已正确应用。这证明 main.scss 已被成功加载一次。
    • Element Plus 定制: <el-button type="primary"> 按钮应显示为我们定义的 $GLColor
    • 组件内工具: 在任意 Vue 组件的 <style lang="scss"> 块中,你都可以直接使用 $GLColor 变量或 @include flex-center; 混入来编写该组件的 独有样式,而无需任何手动导入。

通过这套架构,我们实现了企业级项目的终极目标:一套清晰、高效、可维护的样式体系,它完美平衡了全局一致性与组件独立性。


1.7 核心服务集成

一个应用不仅需要美观的 UI,还需要强大的 “内脏” 来处理数据请求、页面导航和状态管理。本节将为我们的项目集成这些核心服务,搭建起应用的 “神经与循环系统”。

当前任务: 1.7 - 核心服务集成
任务目标: 为项目安装并配置 Axios (网络请求)、Vue Router (路由)、Pinia (客户端状态) 和 TanStack Query (服务端状态)。

1.7.1 网络请求层 (Axios) 封装

在任何项目中,直接在组件中调用 axios.get(...) 都是一种不良实践。我们需要一个统一的、可配置的封装层来处理通用逻辑。

  • 设计思路: 我们将创建一个 axios 实例,并配置通用的 baseURLtimeout。更重要的是,我们将利用 拦截器 (Interceptors) 来实现:
    1. 请求拦截器: 未来用于统一注入用户 token。
    2. 响应拦截器: 用于统一处理返回数据的结构(例如,直接返回 res.data)和集中的错误处理(例如,401 未授权拦截)。
开发者日记
开发中

架构师,我们现在要开始做 API 请求了。我之前的习惯是在需要请求的组件里直接 import axios from 'axios' 然后就用了,这样做有什么问题吗?

架构师

这是一个很常见的起点,但在大型项目中会很快演变成一场灾难。想象一下,如果后端 API 的 baseURL 换了,或者所有请求都需要加一个统一的 header,你是不是得去几十个文件里一个个修改?

…好像是的,那维护成本太高了。

架构师

完全正确。所以,企业级项目的最佳实践,是创建一个全局的、封装好的 axios 实例。我们将所有的基础配置,比如 baseURL、超时时间,都放在这个文件里。更强大的是,我们可以用 “拦截器” 来打造请求的 “自动化流水线”。

“自动化流水线”?听起来很酷。

架构师

是的。比如,请求拦截器可以在每个请求发出去之前,自动检查并带上用户的 token。响应拦截器可以在收到数据后,自动帮架构师剥离掉外层的 data 包装,或者在遇到 401 这种通用错误时,直接弹出提示并跳转到登录页。这样架构师的业务组件就只需要关心业务本身了。

明白了!单一职责原则,让专业的工具做专业的事。那我们这个封装文件应该怎么写?

架构师

很好,你已经领悟到精髓了。我来给你提供一个非常标准的、具备良好扩展性的基础封装结构。

1. 依赖安装

1
pnpm add axios

2. 封装 HTTP 工具模块

src/utils/ 目录下创建 http.ts 文件。

请将以下代码填入 src/utils/http.ts 文件中:

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
// src/utils/http.ts
import axios from "axios";

const httpInstance = axios.create({
baseURL: "http://localhost:3001",
timeout: 5000,
});

// axios 请求拦截器
httpInstance.interceptors.request.use(
(config) => {
// 1. 从 pinia 获取 token 数据
// 2. 按照后端的要求拼接 token 数据
return config;
},
(e) => Promise.reject(e)
);

// axios 响应式拦截器
httpInstance.interceptors.response.use(
// 剥离了一层 data,后续直接拿到的就是响应 data
(res) => res.data,
(e) => {
// 1. 统一错误提示
// 2. 401 token 失效处理
return Promise.reject(e);
}
);

export default httpInstance;

代码解读:

  • axios.create: 创建一个独立的 axios 实例,避免污染全局 axios。
  • interceptors.request: 在请求发送前进行拦截。我们在这里预留了未来添加 token 的逻辑。
  • interceptors.response: 在响应到达 then/catch 之前进行拦截。我们在这里做了两件重要的事:
    1. 成功回调中 res => res.data,这可以让我们在后续调用时直接获取数据,无需再 response.data
    2. 失败回调中预留了统一错误处理逻辑,例如 token 失效后的跳转登录页。

1.7.2 路由系统 (Vue Router)

Vue Router 是 Vue 官方的路由管理器,是构建单页应用 (SPA) 的标准配置。

1. 依赖安装

1
pnpm add vue-router

2. 创建路由配置文件

src/ 目录下创建 router/index.ts 文件。

开发者日记
开发中

架构师,关于路由,我们这个电商项目页面还挺多的。比如有首页、登录页、分类页…我是不是应该给每个页面都创建一个顶级路由,比如 //login/category

架构师

这是个好问题,它涉及到路由设计的核心思想。你看,首页和分类页,它们是不是都有共同的页头和页脚?

是的,都有。登录页没有。

架构师

这就是关键。我们会创建一个 Layout 组件,作为所有 “有公共布局” 页面的父级容器。然后,首页、分类页都作为它的 “子路由” 存在。当用户在这些页面间切换时,实际上只是 Layout 组件内部的一部分在变化,而页头页脚保持不变。这不仅代码复用性好,用户体验也更流畅。登录页则是一个独立的顶级路由。

原来如此!这就是嵌套路由的实际应用场景。那 path 该怎么写?首页的 path 应该是 /home 吗?

架构师

一个小技巧是,当 Layoutpath/ 时,你可以给首页子路由一个空的 path: ''。这样,访问根路径 / 时,就会自动渲染 Layout 并把首页作为它的默认内容。

明白了,这样设计清晰多了。

请将以下代码填入 src/router/index.ts 文件中,并根据需要创建对应的 .vue 文件 (可以先只写一个 <template><div>...</div></template>):

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
// src/router/index.ts
import { createRouter, createWebHistory } from "vue-router";
import Layout from "@/views/Layout/index.vue";
import Login from "@/views/Login/index.vue";

const router = createRouter({
history: createWebHistory(import.meta.env.BASE_URL),
routes: [
{
path: "/",
component: Layout,
children: [
{
path: "", // 默认二级路由
component: () => import("@/views/Home/index.vue"),
},
{
path: "category/:id", // 使用 path parameter 接收 id
component: () => import("@/views/Category/index.vue"),
},
],
},
{
path: "/login",
component: Login,
},
],
});

export default router;

代码解读:

  • createWebHistory: 使用 H5 History 模式,URL 路径更美观。
  • routes: 路由规则数组。我们定义了一个 嵌套路由
    • / 路径会渲染 Layout 组件,它作为所有一级页面的通用布局(包含导航、页头、页脚)。
    • children 数组定义了二级路由。当访问 / 时,由于 path: '',会默认在 Layout 组件的 <RouterView /> 中渲染 Home 组件。

1.7.3 状态管理引擎安装与最终注册

最后,我们安装 PiniaTanStack Query,并在 main.ts 中完成所有核心服务的最终注册。

1. 依赖安装

1
2
pnpm add pinia @tanstack/vue-query
pnpm add pinia-plugin-persistedstate

@tanstack/vue-query 的开发者工具 vue-query-devtools 在生产环境中不会被打包,但为了方便调试,推荐在开发依赖中安装它:pnpm add -D @tanstack/vue-query-devtools

2. 在 main.ts 中完成所有服务注册

这是我们 main.ts 文件的最终形态,它成为了所有核心服务的 “总装车间”。

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
// src/main.ts
import { createApp } from "vue";
import App from "./App.vue";
import router from "./router"; // 导入路由
import { createPinia } from "pinia";
import { VueQueryPlugin } from "@tanstack/vue-query";
import ElementPlus from "element-plus";
import piniaPluginPersistedstate from "pinia-plugin-persistedstate"; // 1. 导入插件
import zhCn from "element-plus/es/locale/lang/zh-cn";

// 核心:导入唯一的全局样式文件
import "@/styles/main.scss";

const app = createApp(App);
const pinia = createPinia();
pinia.use(piniaPluginPersistedstate); // 2. 注册插件
app.use(pinia);
app.use(router); // 注册路由
app.use(VueQueryPlugin); // 注册 TanStack Query

app.use(ElementPlus, {
locale: zhCn,
});

app.mount("#app");

1.8 API 自动导入优化

在【任务 1.3】中,我们已经为项目配置了路径别名,解决了深层相对路径的问题。本节,我们将解决另一个影响开发效率的痛点:在每个 Vue 组件中重复编写 import { ref, computed, ... } from 'vue'

当前任务: 1.8 - API 自动导入优化
任务目标: 配置 unplugin-auto-import 插件,实现对 Vue、Vue Router 等核心库 API 的自动导入,让开发者更专注于业务逻辑。

1.8.1 设计思路:解放 import 语句

unplugin-auto-import 插件的原理是在编译时扫描代码,如果发现你使用了像 ref, computed, onMounted 等未被导入的变量,它会自动检查这些变量是否属于预设库(如 ‘vue’, ‘vue-router’)的导出成员。如果是,它会自动为架构师添加导入语句。为了让 TypeScript 理解这些 “凭空出现” 的全局变量,该插件还会生成一个类型声明文件(.d.ts)。

1.8.2 Vite 配置文件 (vite.config.ts)

我们将扩展 unplugin-auto-import 的配置,明确告知它需要为哪些库开启自动导入。

开发者日记
开发中

架构师,路径别名确实方便多了。但现在我还是很烦,每个组件都要写一遍 import { ref, computed } from 'vue',有没有办法让它也自动化?

架构师

问得好,这正是我们要解决的下一个问题。专业的工作流,就是要消灭一切不必要的重复性劳动。unplugin-auto-import 这个插件,就是为此而生的。

之前我们安装 Element Plus 的时候已经装过它了。

架构师

是的,当时我们只用了它的基础功能。现在,我们要深入配置它,让它不仅处理 Element Plus 的 API,更能处理 Vue 和 Vue Router 的所有核心 API。我们还需要配置类型声明的生成路径,确保 TypeScript 和 IDE 能够正确识别这些 “凭空出现” 的全局变量。

明白了,这样我们就彻底告别手动 import 了!

架构师

完全正确!一旦配置完成,你在任何组件中都可以直接使用 ref, computed, useRouter 等 API,就像它们是全局变量一样自然。

现在,让我们扩展 vite.config.tsunplugin-auto-import 的配置:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// vite.config.ts
// ... imports
import AutoImport from "unplugin-auto-import/vite";
// ...

export default defineConfig({
plugins: [
vue(),
// 扩展 unplugin-auto-import 配置
AutoImport({
// 1. 指定需要自动导入的库
imports: ["vue", "vue-router"],
// 2. 指定生成 d.ts 文件的位置
dts: "src/auto-imports.d.ts",
// 3. 配置 ElementPlusResolver
resolvers: [ElementPlusResolver()],
}),
Components({
// ... existing config
}),
// ... other plugins
],
// ... resolve and css config
});

配置解读:

  • imports: ['vue', 'vue-router']: 我们明确告诉插件,自动扫描并导入 vuevue-router 这两个库导出的所有 API。
  • dts: 'src/auto-imports.d.ts': 这是至关重要的一步。插件会创建一个 auto-imports.d.ts 文件,其中包含了所有自动导入 API 的 TypeScript 类型声明。

1.8.3 TypeScript 类型支持

我们需要确保 TypeScript 配置文件能够识别这个自动生成的类型声明文件。

tsconfig.app.json 文件中,确保 include 数组包含了 auto-imports.d.ts

1
2
3
4
5
6
7
8
9
10
11
12
// tsconfig.app.json
{
// ... existing config
"include": [
"src/**/*.ts",
"src/**/*.d.ts",
"src/**/*.tsx",
"src/**/*.vue",
"auto-imports.d.ts",
"components.d.ts"
]
}

1.8.4 功能测试

现在,让我们创建一个测试组件来验证自动导入是否正常工作:

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
<!-- src/components/AutoImportTest.vue -->
<script setup lang="ts">
// 注意:我们没有手动导入任何东西!
const count = ref(0);
const doubleCount = computed(() => count.value * 2);
const router = useRouter();

const increment = () => {
count.value++;
};

const goHome = () => {
router.push("/");
};

onMounted(() => {
console.log("组件已挂载,自动导入测试成功!");
});
</script>

<template>
<div>
<p>Count: {{ count }}</p>
<p>Double Count: {{ doubleCount }}</p>
<el-button @click="increment">增加</el-button>
<el-button @click="goHome">回到首页</el-button>
</div>
</template>

重启 Vite 开发服务器后,如果上述代码能够正常运行且 VS Code 提供了完整的类型提示,说明自动导入配置成功!

配置成功标志: 当你输入 refcomputed 等 API 时,VS Code 会自动提供智能提示,且无需手动导入即可正常使用。同时,你会发现项目根目录生成了 auto-imports.d.ts 文件。

最终完整的 vite.config.ts

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
// vite.config.ts
import { fileURLToPath, URL } from "node:url";
import { defineConfig } from "vite";
import vue from "@vitejs/plugin-vue";

// 1. 引入所需插件
import AutoImport from "unplugin-auto-import/vite";
import Components from "unplugin-vue-components/vite";
import { ElementPlusResolver } from "unplugin-vue-components/resolvers";

// 引入 unplugin-icons
import Icons from "unplugin-icons/vite";
import IconsResolver from "unplugin-icons/resolver";

export default defineConfig({
plugins: [
vue(),
// 2. 配置 unplugin-auto-import
AutoImport({
// 1. 指定需要自动导入的库
imports: ["vue", "vue-router"],
// 2. 指定生成 d.ts 文件的位置
dts: "src/auto-imports.d.ts",
// 3. 配置 ElementPlusResolver
resolvers: [ElementPlusResolver()],
}),
// 3. 配置 unplugin-vue-components
Components({
resolvers: [
// 自动导入 Element Plus 组件
ElementPlusResolver({ importStyle: "sass" }),
// 自动导入 Element Plus 图标
// 我们约定图标组件的使用方式是 <i-ep-iconName />
IconsResolver({
prefix: "i", // 默认为 i, 可不写
enabledCollections: ["ep"], // ep 是 element-plus 的图标库
}),
],
}),
// 4. 配置 unplugin-icons
Icons({
autoInstall: true,
}),
],
resolve: {
alias: {
"@": fileURLToPath(new URL("./src", import.meta.url)),
},
},

server: {
proxy: {
// 代理 API 请求到 Mock 服务器
"/api": {
target: "http://localhost:3001",
changeOrigin: true,
rewrite: (path) => path.replace(/^\/api/, ""),
},
// 代理图片资源到 Mock 服务器
"/images": {
target: "http://localhost:3001",
changeOrigin: true,
},
},
},
// 2. SCSS 自动化注入配置
css: {
preprocessorOptions: {
scss: {
// 只注入变量和 mixins,避免重复导入 CSS 输出
additionalData: `
@use "@/styles/abstracts/variables" as *;
@use "@/styles/abstracts/mixins" as *;
@use "@/styles/abstracts/utilities" as *;
`,
},
},
},
});

1.9 自动化提交流程

IDE 级别的配置能提升个人效率,但无法保证团队协作的一致性。我们需要在代码流入版本库的最后一道关卡——git commit——设立 “自动化哨兵”,强制所有提交都遵循统一的质量和规范标准,确保代码仓库的纯净与专业。

当前任务: 1.9 - 自动化提交流程
任务目标: 初始化 Git 仓库并推送到 GitHub,然后集成 Husky, lint-staged, commitlint,构建一个在提交代码前自动执行 “代码格式化”、“质量检查” 和 “提交信息规范校验” 的自动化工作流。

1.9.1 设计思路:三道防线

我们将建立三道质量防线:

  1. 第一道防线 - 预提交检查 (pre-commit Hook): 在 git commit 执行前,自动对暂存区的文件运行 ESLintPrettier,确保所有即将提交的代码都符合质量和风格标准。
  2. 第二道防线 - 提交信息校验 (commit-msg Hook): 校验提交信息是否遵循约定式提交规范,确保提交历史的专业性和可读性。
  3. 第三道防线 - 推送前检查: 在推送到远程仓库前,可以添加额外的检查,如运行完整的测试套件。

1.9.2 Git 仓库初始化与远程连接

首先,我们需要为项目创建 Git 仓库并连接到远程仓库:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 1. 初始化 Git 仓库
git init

# 2. 添加所有文件到暂存区
git add .

# 3. 创建初始提交
git commit -m "feat: initial project setup with Vue 3 + TypeScript"

# 4. 连接到远程 GitHub 仓库(请替换为你的仓库地址)
git remote add origin https://github.com/your-username/vue3-webshop.git

# 5. 推送到远程仓库
git push -u origin main

1.9.3 依赖安装

现在安装所有自动化工具:

1
pnpm add -D husky lint-staged @commitlint/cli @commitlint/config-conventional

依赖解读:

包名核心职责
huskyGit Hooks 工具,让我们能轻松地在 pre-commitcommit-msg 等 Git 事件上挂载自定义脚本。
lint-staged一个只对 Git 暂存区 内的文件执行任务的工具,极大地提升了检查效率。
@commitlint/clicommitlint 的命令行工具,用于校验提交信息。
@commitlint/config-conventionalcommitlint 的一个预设配置包,它规定了我们必须遵循 “约定式提交” 规范。

1.9.4 配置 Huskylint-staged (代码检查)

  1. 初始化 Husky:

    1
    pnpm exec husky init
  2. 配置 lint-staged:
    package.json 中添加 lint-staged 配置:

    1
    2
    3
    4
    5
    6
    7
    8
    // package.json
    {
    // ... existing config
    "lint-staged": {
    "*.{js,jsx,ts,tsx,vue}": ["eslint --fix --cache", "prettier --write"],
    "*.{json,md,scss,css}": ["prettier --write"]
    }
    }
  3. 创建预提交钩子:
    修改 .husky/pre-commit 文件:

    1
    2
    # .husky/pre-commit
    npx lint-staged

工作原理解读:

  • lint-staged 只会对 Git 暂存区(git add 后的文件)执行检查,大大提升了性能。
  • 对于 JS/TS/Vue 文件,先运行 eslint --fix 自动修复问题,再用 prettier 统一格式。
  • 对于配置文件和文档,只运行 prettier 格式化。

1.9.5 配置 commitlint (提交信息规范)

  1. 约定式提交规范说明:
    我们要求所有提交信息都遵循 type(scope): subject 的格式。这不仅使提交历史清晰可读,更是自动化生成 CHANGELOG 和版本管理的基础。

    类型 (type)含义
    feat新增功能
    fix修复 Bug
    docs文档变更
    style代码风格调整(不影响逻辑)
    refactor代码重构
    perf性能优化
    test新增或修改测试
    chore构建流程、辅助工具的变更
  2. 创建 commitlint.config.js:
    在项目根目录创建配置文件:

    1
    2
    3
    4
    // commitlint.config.js
    export default {
    extends: ["@commitlint/config-conventional"],
    };
  3. 创建提交信息钩子:
    创建 .husky/commit-msg 文件:

    1
    2
    # .husky/commit-msg
    npx --no-install commitlint --edit $1

1.9.6 添加便捷脚本

为了方便开发,在 package.json 中添加一些实用的脚本:

1
2
3
4
5
6
7
8
9
10
11
// package.json
{
"scripts": {
"dev": "vite",
"build": "vue-tsc -b && vite build",
"preview": "vite preview",
"lint": "eslint . --fix",
"lint:check": "eslint .",
"format": "prettier --write ."
}
}

1.9.7 完整测试流程

现在让我们测试整个自动化工作流是否正常:

  1. 故意制造一些格式问题: 在任意 .vue 文件中添加一些不规范的代码(如缺少分号、不一致的引号等)。

  2. 测试预提交检查:

    1
    2
    git add .
    git commit -m "test commit"

    你应该看到 lint-staged 自动运行,修复代码格式问题。

  3. 测试提交信息校验:

    执行一次 不符合规范 的提交:git commit -m "test"

    • 预期结果: 终端会提示 subject may not be empty, type may not be empty 等错误,提交被 中止

    执行一次 符合规范 的提交:git commit -m "feat: setup automated commit workflow"

    • 预期结果:
      lint-staged 首先运行,你会看到 eslintprettier 的输出,格式问题被自动修复。
      commitlint 校验通过。提交成功!

成功标志: 当你能够成功提交一个符合规范的 commit,并且在过程中看到代码被自动格式化,说明整个自动化提交流程已经完美搭建完成!