项目搭建
第一章:项目初始化
一、创建项目
使用 Vite 搭建项目。
1)安装 pnpm 包管理器
npm install pnpm -g2)创建项目并启动
pnpm create vite

切换到项目文件夹下,安装依赖,启动项目
# 切换到项目文件夹下
cd vue3-admin
# 安装依赖
pnpm install
# 启动项目
pnpm run dev
启动后访问 http://localhost:5173/
二、工具集成
ESLint 专注于代码质量与逻辑问题检测;Prettier 专注于代码格式化;EditorConfig 帮助统一编码风格;Husky 通过 Git 钩子强制执行代码检查;Lint-staged 仅对暂存文件应用 linters;Commitlint 则确保提交信息遵循一定的规范。
这些工具共同构成了一个强大的代码质量和提交规范流程。
1. eslint 集成
通过 npx 安装 eslint
npx eslint --init配置如下:

安装好后,会生成 eslint.config.js 文件,可在里面进行 eslint 的相关配置:
import globals from "globals"
import pluginJs from "@eslint/js" // 推荐的js规范
import tseslint from "typescript-eslint" // 推荐的ts规范
import pluginVue from "eslint-plugin-vue" // 推荐的vue的规范
// mjs --> esModule, cjs --> commonjs
export default [
{ files: ["**/*.{js,mjs,cjs,ts,vue}"] },
/* {
ignores: [".css", "*.d.ts"]
}, */
{
languageOptions: {
globals: {
...globals.browser,
...globals.node
}
}
},
pluginJs.configs.recommended,
...tseslint.configs.recommended,
...pluginVue.configs["flat/essential"],
{
files: ["**/*.vue"],
languageOptions: { parserOptions: { parser: tseslint.parser } } // 校验 vue 中的 ts 代码
},
{
// 自定义规则, 根据需要增加 eslint 规则
rules: {
"no-console": "warn", // 在代码中若使用 console 对象则警告
"vue/multi-word-component-names": "off" // 关闭 Vue 组件名称必须由多个单词组成的规则检查
}
}
]如下图,在 vscode 中搜索 eslint 插件,安装 ESLint 插件。

安装后,点击图标,选择【添加到工作区建议】。

点击后,会在项目文件夹 .vscode 下的 extensions.json 中添加配置,没有的话手动添加一下。

在 package.json 中添加脚本,即可通过 npm run lint 进行校验,代码文件中也会有提示。



2. prettier 集成
prettier 是一个代码格式化工具,用于统一代码风格。通过 pnpm 安装 prettier 相关插件:
pnpm add prettier eslint-plugin-prettier eslint-config-prettier -Deslint-plugin-prettier 是一个 ESLint 插件,它允许 ESLint 使用 Prettier 的规则来检查代码风格。
eslint-config-prettier 是一个 ESLint 配置,用于关闭 ESLint 中与 Prettier 冲突的规则,确保两者能和谐工作,避免规则冲突。

安装后,在项目文件夹下新建 prettier 配置文件 prettier.config.js。
export default {
singleQuote: false, // 使⽤单引号
semi: false, // 末尾添加分号
tabWidth: 2,
trailingComma: "none",
useTabs: false,
endOfLine: "auto",
};在 eslint.config.js 文件中添加 prettier 相关配置。
import prettierRecommended from "eslint-plugin-prettier/recommended" // prettier集成中安装的插件
export default [
// 省略之前的代码 ......
prettierRecommended /*
作用一: 覆盖掉 eslint 的规范
作用二: 启用 eslint-config-prettier 配置,该配置将关闭与 Prettier 冲突的 ESLint 规则
*/
]如下图,在 vscode 中搜索 Prettier 插件,安装 Prettier 插件。

安装后,点击图标,选择【添加到工作区建议】。

点击后,会在项目文件夹 .vscode 下的 extensions.json 中添加配置,没有的话手动添加一下。

在设置中,搜索 formatter ,选择 Prettier-Code formatter。

同时,选择保存时设置文件格式。

这样,以后在保存文件时,就会使用 Prettier 进行代码格式化。
3. EditorConfig 插件
如下图,在 vscode 中搜索 EditorConfig 插件,安装 EditorConfig 插件。
EditorConfig 通过 .editorconfig 文件定义编码风格,如缩进、行尾字符等,确保不同编辑器和开发者使用统一风格。支持多种编程语言和编辑器,配置简单,有助于提升代码一致性。

安装后,点击图标,选择【添加到工作区建议】。

点击后,会在项目文件夹 .vscode 下的 extensions.json 中添加配置,没有的话手动添加一下。

在项目文件夹下新建配置文件 .editorconfig 。
root = true
[*]
charset = utf-8
indent_style = space
indent_size = 2
end_of_line = lf这样配置以后,所有协同开发人员下载代码后,会下载同样的插件,使用同样的代码检测配置,保证团队所有人员使用同一种风格进行开发。
4. Husky 及 lint-staged 集成
Husky 是一款简化 Git 钩子管理的工具,让开发者在 Git 操作(如提交、推送)前自动执行代码检查、格式化等任务。它易于集成,提高了代码质量和团队协作效率,是现代开发工作流程中的常用组件。
lint-staged 是一个在 Git 暂存文件上运行 linters 的工具,确保只有暂存的文件被检查和格式化,提高了开发流程的效率,并保持代码库的一致性。通过配置 .lintstagedrc 文件,可以指定不同文件类型对应的处理工具,如 ESLint、Prettier 等。结合 Husky 使用,可在提交前自动执行代码质量检查。
通过 pnpm 安装 Husky 及 lint-staged 插件:
pnpm install husky lint-staged -D
在 package.json 中配置 lint-staged,在代码提交前对 js,cjs,ts,vue 文件进行 eslint 校验并尽可能修复,对 html,json,css,scss 文件使用 prettier 进行代码格式化。

然后使用 husky 配置 git 钩子。
npx husky initinit 后会生成一个 .husky 文件夹,在该文件夹下配置各种 git 钩子,pre-commit 文件是自动生成的,修改其配置。
如下图,配置好后,在执行 git commit 时,会先进行代码校验,校验通过后才可以提交。
修改报错前:

修改报错后:

5. commitlint 集成
commitlint 是一个确保 Git 提交消息遵循特定规则的工具,通过预定义的配置来强制执行提交消息格式,有助于维护代码库的提交历史清晰和一致性。结合 Git 钩子,如 Husky,可在提交前自动检查消息是否符合规范。
@commitlint/config-conventional 是一个用于 Commitlint 的预设配置,遵循 Angular 提交消息规范。它帮助团队保持提交消息的一致性,便于生成清晰的变更日志,同时支持语义化版本控制。通过集成此配置,开发者在提交代码时需遵循类型(feat、fix 等)、作用域、主题、正文和脚注的结构。
通过 pnpm 安装 commitlint 插件:
pnpm install @commitlint/cli @commitlint/config-conventional -D
在项目文件夹下新建配置文件 commitlint.config.cjs。
module.exports = {
extends: ["@commitlint/config-conventional"]
};在 husky 下添加 git 钩子,在 .husky 文件夹下 新增 commit-msg 文件,修改内容如下

npx commitlint --edit $1不符合规范时禁止提交:

符合规范时提交成功:

三、配置
1. 配置别名
在 vite.config.ts 中配置别名,这样后续写路径更便捷
// vite.config.ts
import { defineConfig } from "vite";
import vue from "@vitejs/plugin-vue";
import path from "path";
// https://vite.dev/config/
export default defineConfig({
resolve: {
alias: [{ find: "@", replacement: path.resolve(__dirname, "src") }]
},
plugins: [vue()]
});这样路由中配置可修改成
// router/index.ts
import {
createRouter,
createWebHistory,
type RouteRecordRaw
} from "vue-router";
const routes: RouteRecordRaw[] = [
{
path: "/home",
component: () => import("@/views/Home.vue")
},
{
path: "/about",
component: () => import("@/views/About.vue")
}
];
export default createRouter({
routes, // 路由表
history: createWebHistory() // 路由模式
});2. 配置 tsconfig
上面配置别名后,代码可以正常运行,但代码中会出现 TypeScrip 的飘红报错,还需修改一下 TypeScrip 的配置。

如图,TypeScrip 的配置文件有三个。

在 Vue 3 项目中,这三个 TypeScript 配置文件有不同的用途:
tsconfig.json:这是项目的根配置文件,定义了 TypeScript 编译器的全局选项,比如编译目标、模块系统、包含和排除的文件等。tsconfig.app.json:这个文件通常继承自tsconfig.json,并为 Vue 应用的前端源代码提供特定的编译选项。它可能包含针对前端构建的优化设置,比如特定的路径别名或不同的编译输出目录。tsconfig.node.json:这个文件同样继承自tsconfig.json,但它是为 Node.js 环境中的代码(如服务器端渲染或构建脚本)设计的。它可能包含 Node.js 特定的编译选项,比如不同的模块解析策略。
简而言之,tsconfig.json 是基础配置,而 tsconfig.app.json 和 tsconfig.node.json 是针对不同运行环境的定制配置。
这里我们配置 tsconfig.app.json,添加 baseUrl 和 paths。
// tsconfig.app.json
{
"extends": "@vue/tsconfig/tsconfig.dom.json",
"compilerOptions": {
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
/* Linting */
"strict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noFallthroughCasesInSwitch": true,
"noUncheckedSideEffectImports": true,
"baseUrl": ".",
"paths": {
"@/*": ["src/*"]
}
},
"include": ["src/**/*.ts", "src/**/*.tsx", "src/**/*.vue"]
}配置好以后,就不会报错了。(如果还有飘红,就重启编辑器即可)
四、VSCode 插件
1. Vue (official)
在 vscode 中搜索 Vue (official) 插件,安装 Vue-official 插件。
.ESSEN-7V.png)
Vue (official)是 Vue 的 VSCode 官方插件,原名 Volar,提供语法高亮、代码片段、智能感知和错误检查等功能,支持 Vue 2 和 Vue 3,特别适用于 Vue3.4 以上版本,新增了对Vue3.4 新特性的支持,如属性同名简写和拖拽导入组件。该插件集成了 Vetur 的功能并有所增强,建议在 Vue 3 项目中使用时禁用 Vetur。
安装后,点击设置图标,选择【添加到工作区建议】。
-%E6%B7%BB%E5%8A%A0%E5%88%B0%E5%B7%A5%E4%BD%9C%E5%8C%BA%E5%BB%BA%E8%AE%AE.BxK09iFZ.png)
第二章:项目依赖
一、Vue Router
1)安装 vue-router
通过 pnpm 安装 Vue Router
pnpm i vue-router
2)配置 Router
在 src 文件夹下新建 views 文件夹,新建文件 Home.vue 和 About.vue



在 src 文件夹下新建 router 文件夹,在 router 下新建 index.ts 用来配置路由

// router/index.ts
import {
createRouter,
createWebHistory,
type RouteRecordRaw
} from "vue-router";
const routes: RouteRecordRaw[] = [
{
path: "/home",
component: () => import("@/views/Home.vue")
},
{
path: "/about",
component: () => import("@/views/About.vue")
}
];
export default createRouter({
routes, // 路由表
history: createWebHistory() // 路由模式
});3)引用 Router
在 main.ts 中引入路由
//main.ts
import { createApp } from "vue";
import App from "./App.vue";
import router from "./router";
const app = createApp(App);
app.use(router);
app.mount("#app");在 App.vue 中添加入口
<!-- App.vue -->
<script setup lang="ts">
import { RouterView } from "vue-router";
</script>
<template>
<RouterLink to="/home">首页</RouterLink>
<RouterLink to="/about">关于</RouterLink>
<RouterView></RouterView>
</template>二、Pinia
1)安装 Pinia
pnpm install pinia2)在 src 文件夹下新建文件夹 stores,然后在新建文件 counter.ts
// counter.ts
import { defineStore } from "pinia";
import { ref } from "vue";
export const useCounterStore = defineStore("counter", () => {
// vue3中的setup函数
const count = ref(0);
const increment = () => {
count.value++;
};
return { count, increment };
});3)引用 Pinia
在 main.ts 中引入 Pinia
// main.ts
import { createApp } from "vue";
import App from "./App.vue";
import router from "./router";
import { createPinia } from "pinia";
const app = createApp(App);
const pinia = createPinia();
app.use(router);
app.use(pinia);
app.mount("#app");在 App.vue 中应用
<!-- App.vue -->
<template>
<RouterLink to="/home">首页</RouterLink>
<RouterLink to="/about">关于</RouterLink>
{{ store.count }}
<button @click="store.increment">+</button>
<RouterView></RouterView>
</template>
<script lang="ts" setup>
import { useCounterStore } from "./stores/counter";
const store = useCounterStore();
</script>
<style scoped></style>三、Element Plus
此处是 Element Plus 完整引入。
1)安装 element-plus
pnpm install element-plus2)引用 element-plus
在 main.ts 中引入 element-plus
// main.ts
import { createApp } from "vue";
import App from "./App.vue";
import router from "./router";
import { createPinia } from "pinia";
import ElementPlus from "element-plus";
import "element-plus/dist/index.css";
const app = createApp(App);
const pinia = createPinia();
app.use(router);
app.use(pinia);
app.use(ElementPlus);
app.mount("#app");3)修改 tsconfig.app.json 配置,增加 element-plus 类型提示

配置好后,在页面中应用 element-plus 组件就会有提示,这样可以提高开发效率。
4)在 App.vue 中应用
<!-- App.vue -->
<template>
<RouterLink to="/home">首页</RouterLink>
<RouterLink to="/about">关于</RouterLink>
{{ store.count }}
<button @click="store.increment">+</button>
<RouterView></RouterView>
<el-button type="primary">按钮</el-button>
</template>
<script lang="ts" setup>
import { useCounterStore } from "./stores/counter";
const store = useCounterStore();
</script>
<style scoped></style>第三章:页面布局及样式处理
将之前集成项目依赖的时候,所创建的测试文件 Home.vue、About.vue、counter.ts 删除,然后在 App.vue 中删除其引用。
一、整体页面布局
页面整体布局构成了产品的框架基础,通常涵盖主导航、侧边栏菜单、面包屑导航以及核心内容展示区域等关键元素。
1. 创建 layout 布局组件
在 src 文件夹下新建文件夹 layout,用于放置页面整体布局相关的组件页面。在 layout 文件夹下新建文件 index.vue。
<!-- layout/index.vue -->
<template>
<div class="app-wrapper">
<div class="sidebar-container">侧边导航</div>
<div class="main-container">
<div class="header">
<div class="navbar">导航条1</div>
<div class="tags-view">导航条2</div>
</div>
<div class="app-main">
<router-view></router-view>
</div>
</div>
</div>
</template>2. 创建 Dashboard 页面
1)创建页面文件
删除 main.ts 中引用的默认样式 style.css,在 views 文件夹下新建 Dashboard 文件夹,在Dashboard 文件夹下新建文件 index.vue 文件。

2)配置路由
修改 router/index.ts 。
// router/index.ts
import {
createRouter,
createWebHistory,
type RouteRecordRaw
} from "vue-router";
import Layout from "@/layout/index.vue";
const routes: RouteRecordRaw[] = [
{
path: "/",
component: Layout,
redirect: "/dashboard",
children: [
{
path: "dashboard",
name: "Dashboard",
component: () => import("@/views/Dashboard/index.vue")
}
]
}
];
export default createRouter({
routes, // 路由表
history: createWebHistory() // 路由模式
});3)启动项目
启动项目,查看页面。
npm run dev
二、样式处理
样式使用 Sass + Unocss 编写,使用前先安装 Sass 和 Unocss。
1. 安装 Sass
Sass 是一种强大的 CSS 预处理器,它扩展了 CSS 的功能,增加了变量、嵌套规则、混合(mixins)、继承、函数等高级特性。Sass 提供了更高效、更易于维护的样式编写方式。通过编译,Sass 代码转换为标准的 CSS,兼容所有浏览器。Sass 有两种语法格式:SCSS(类似于 CSS 的语法)和 Indented Sass(缩进语法)。它广泛应用于大型项目和框架中,有助于提高开发效率和代码的可读性。
使用 pnpm 安装 Sass
pnpm install sass -D
2. 安装 UnoCSS
UnoCSS 是一个即时按需的原子化 CSS 引擎,它提供了一种灵活、强大、快速且愉快的方式来生成和定制 CSS。这个引擎的特点是不需要解析、抽象语法树(AST)或扫描,因此它在性能上比其他类似的 CSS 引擎(如 Windi CSS 或 Tailwind CSS JIT)快五倍。UnoCSS 是轻量级的,零依赖,并且对浏览器友好,其大小约为 6kb(使用 minbrotli 压缩)。
@unocss/preset-wind3是 UnoCSS 的一个预设,它提供了一系列基于原子化 CSS 的实用类。这个预设包含了大量的通用样式规则,如颜色、间距、字体大小、边框、布局等,使得开发者可以快速应用这些样式到 HTML 元素上,而无需编写具体的 CSS 代码。@unocss/preset-wind3的目标是提供一种简洁、直观的方式来构建界面,同时保持高性能和低维护成本。@unocss/preset-attributify是另一个 UnoCSS 预设,它允许开发者使用属性(attributes)而不是类(classes)来应用原子化 CSS 样式。这种方法的优点是可以减少 HTML 中的类名数量,使标记更加简洁,并且可以更容易地应用动态样式。例如,你可以直接在 HTML 元素上使用bg-red、text-lg等属性来改变背景颜色或文本大小,而不需要在元素上添加相应的类。这使得 UnoCSS 的使用更加灵活,尤其是在处理动态内容和响应式设计时。
使用 pnpm 安装 UnoCSS 和预设。
pnpm i unocss @unocss/preset-wind3 @unocss/preset-attributify -D安装好后,在项目根目录下新建配置文件 uno.config.ts 用于对 Unocss 进行配置。
// uno.config.ts
import { defineConfig } from "unocss";
import presetAttributify from "@unocss/preset-attributify";
import presetWind3 from '@unocss/preset-wind3'
export default defineConfig({
presets: [presetAttributify(), presetWind3()],
});在 vite.config.ts 中引入。
// vite.config.ts
import { defineConfig } from "vite";
import vue from "@vitejs/plugin-vue";
import path from "path";
import UnoCSS from "unocss/vite";
// https://vite.dev/config/
export default defineConfig({
resolve: {
alias: [{ find: "@", replacement: path.resolve(__dirname, "src") }]
},
plugins: [
vue(),
UnoCSS({
configFile: "./UnoCSS.config.ts"
})
]
});在 main.ts 中导入 Unocss 的样式文件。
// main.ts
import { createApp } from "vue";
import App from "./App.vue";
import router from "./router";
import { createPinia } from "pinia";
import ElementPlus from "element-plus";
import "element-plus/dist/index.css";
import "virtual:uno.css"
const app = createApp(App);
const pinia = createPinia();
app.use(router);
app.use(pinia);
app.use(ElementPlus);
app.mount("#app");接着安装 unocss 插件,这样在代码中会有 unocss 的代码提示。

在页面中使用 Unocss,如在 layout.vue 中添加 text-red 属性。

效果如下:

3. Sass + Unocss 结合使用
Unocss 可以在页面内添加到 class 中使用,或直接如上文所示以行内属性的形式使用,但这样不好维护,为了便于维护,我们希望可以在 <style></style> 中使用,这样既可以享受 Sass 的语法优势,也可以享受 Unocss 的语法优势,也更方便维护。为了达到这个目的,需要安装一个插件 @unocss/transformer-directives 。(一般不建议 Sass 和 Unocss 一起使用,但是一起用还挺方便的,看个人需求吧 ~)
@unocss/transformer-directives是 UnoCSS 的一个转换器插件,用于处理 CSS 文件中的特定指令,如@apply、@screen和theme()。它允许开发者将原子化类应用到自定义选择器,通过媒体查询条件应用样式,以及访问主题配置。这样,UnoCSS 的功能得以扩展,使得样式编写更加灵活和强大,同时保持代码的简洁和可维护性。
使用 pnpm 安装 @unocss/transformer-directives
pnpm add -D @unocss/transformer-directives在 uno.config.ts 中引入
// uno.config.ts
import { defineConfig } from "unocss";
import presetAttributify from "@unocss/preset-attributify";
import presetUno from "@unocss/preset-uno";
import transformDirective from "@unocss/transformer-directives";
export default defineConfig({
presets: [presetAttributify(), presetUno()],
transformers: [transformDirective()], // @apply、@screen 和 theme() 指令的 UnoCSS 转换器
});在页面 <style></style> 中使用
<!-- layout/index.vue -->
<template>
<div class="app-wrapper">
<div class="sidebar-container">侧边导航</div>
<div class="main-container">
<div class="header">
<div class="navbar">导航条1</div>
<div class="tags-view">导航条2</div>
</div>
<div class="app-main">
<router-view></router-view>
</div>
</div>
</div>
</template>
<style lang="scss">
.app-wrapper {
@apply text-red w-full h-full;
}
</style>效果如下:

4. 清除默认样式
现在,项目中还存在一些默认样式,为了避免对后续样式的影响,需要对这些默认样式进行清除,这里我们使用 pnpm 安装 Normalize.css 插件来清除默认样式
Normalize.css 是一个现代的CSS重置方案,旨在提高网页在不同浏览器中的样式一致性。它保留有用的浏览器默认样式,同时修复常见浏览器间的差异和 bug,确保 HTML 元素在多种环境下表现一致。与传统的CSS Reset不同,Normalize.css 更加细腻,专注于提升可用性和可访问性,适用于追求一致性和标准化的项目。它被广泛用于众多框架和网站,如 Twitter Bootstrap 和 HTML5 Boilerplate,是现代网页开发的必备工具。
pnpm add normalize.css在 main.ts 中引入
// main.ts
import { createApp } from "vue";
import App from "./App.vue";
import router from "./router";
import "normalize.css/normalize.css";
import { createPinia } from "pinia";
import ElementPlus from "element-plus";
import "element-plus/dist/index.css";
import "virtual:uno.css"
const app = createApp(App);
const pinia = createPinia();
app.use(router);
app.use(pinia);
app.use(ElementPlus);
app.mount("#app");引入后,可以看见默认的样式已经清除(空隙等)。

5. 完善 layout 样式
完善 layout.vue 的样式
<!-- layout.vue -->
<template>
<div class="app-wrapper">
<div class="sidebar-container">侧边导航</div>
<div class="main-container">
<div class="header">
<div class="navbar">导航条1</div>
<div class="tags-view">导航条2</div>
</div>
<div class="app-main">
<router-view></router-view>
</div>
</div>
</div>
</template>
<style lang="scss">
.app-wrapper {
@apply flex w-full h-full;
.sidebar-container {
@apply bg-red w-[210px];
}
.main-container {
@apply flex flex-col flex-1;
}
.header {
@apply h-84px;
.navbar {
@apply h-50px bg-yellow;
}
.tags-view {
@apply h-34px bg-blue;
}
}
.app-main {
@apply bg-cyan;
min-height: calc(100vh - 84px);
}
}
</style>效果如下:

6. 提取公共变量
Vite 支持 CSS Modules,使用起来非常简单,它允许你将 CSS 类名局部化,从而避免全局冲突。在 Vite 中使用 CSS Modules 的步骤大致如下:
- 命名 CSS 文件 :CSS Modules 要求你的样式文件以
.module.css为后缀。例如,如果你有一个名为styles.css的文件,你需要将其重命名为styles.module.css。- 编写 CSS :在
styles.module.css文件中,你可以像往常一样编写 CSS。- 组件中导入和使用:在你的组件中,你可以导入这个 CSS 文件,Vite 会自动将其识别为 CSS Modules。然后你可以像使用对象一样使用这些样式。
在 src 下新建文件夹 style,新建文件 variables.module.scss,提取公共变量,scss 中声明好的变量在 js 中也可以使用。
// variable.module.scss
$sideBarWidth: 210px;
$navBarHeight: 50px;
$tagsViewHeight: 34px;在 style 下创建 index.scss 文件,引入 variables.modules.scss,在 :root 中定义 css 的变量。
// index.scss
// @import "./variables.module.scss"; // 新版 scss 已经弃用了 @import 语法
@use "./variables.module.scss" as *; // 新版 scss 用 @use 代替 @import
:root {
--sidebar-width: #{$sideBarWidth};
--navbar-height: #{$navBarHeight};
--tagsview-height: #{$tagsViewHeight};
}在 main.ts 中引入 index.scss 。
// main.ts
import { createApp } from "vue";
import App from "./App.vue";
import router from "./router";
import "normalize.css/normalize.css";
import { createPinia } from "pinia";
import ElementPlus from "element-plus";
import "element-plus/dist/index.css";
import "@/style/index.scss";
import "uno.css";
const app = createApp(App);
const pinia = createPinia();
app.use(router);
app.use(pinia);
app.use(ElementPlus);
app.mount("#app");修改 layout.vue 中相关 css 的值
<!-- layout.vue -->
<template>
<div class="app-wrapper">
<div class="sidebar-container">侧边导航</div>
<div class="main-container">
<div class="header">
<div class="navbar">导航条1</div>
<div class="tags-view">导航条2</div>
</div>
<div class="app-main">
<router-view></router-view>
</div>
</div>
</div>
</template>
<style lang="scss">
.app-wrapper {
@apply flex w-full h-full;
.sidebar-container {
@apply bg-red w-[var(--sidebar-width)];
}
.main-container {
@apply flex flex-col flex-1;
}
.header {
@apply h-84px;
.navbar {
@apply h-[var(--navbar-height)] bg-yellow;
}
.tags-view {
@apply h-[var(--tagsview-height)] bg-blue;
}
}
.app-main {
@apply bg-cyan;
min-height: calc(100vh - var(--tagsview-height) - var(--navbar-height));
}
}
</style>修改后效果

第四章:组件及 API 自动导入
目前,项目中的 Element Plus 组件库是全部导入,而我们希望按需导入。因为按需导入 Element Plus 组件库能显著减小应用体积,提高加载速度,优化资源利用。它让项目结构更模块化,降低维护成本,减少潜在冲突。
一、安装插件
首先,屏蔽掉之前全部引入的代码。

通过 pnpm 安装 unplugin-vue-components 和 unplugin-auto-import 插件,可自动按需导入,无需手动操作,兼顾开发效率与性能优化。
unplugin-vue-components 是一个 Vue 3 的组件自动导入插件。它的主要功能如下:
- 自动导入组件:无需手动导入每个 Vue 组件,插件会自动识别并在需要时导入。
- 按需加载:只导入项目中实际使用的组件,减少应用的整体大小。
- 支持多种 UI 库:内置了对许多流行 UI 库的支持,如 Element Plus、Vuetify、Ant Design Vue 等。
- 简化项目配置:通过简单的配置,即可实现组件的自动导入,减少项目配置的复杂性。
unplugin-auto-import 是一个用于自动导入 Vue 3 API 和其他库的 API 的插件。它的主要功能包括:
- 自动导入 API:无需手动导入 Vue 3 的 API(如 ref, reactive, onMounted 等),插件会自动处理。
- 减少冗余代码:减少项目中重复的导入语句,使代码更加简洁。
- 支持自定义配置:可以配置需要自动导入的 API,以及排除某些不需要自动导入的 API。
- 兼容性:与 TypeScript 和其他构建工具(如 Vite、Webpack)兼容。
pnpm install -D unplugin-vue-components unplugin-auto-import二、普通组件自动导入配置
1. Element Plus 按需自动导入
1)配置
在 vite.config.ts 中引入
// vite.config.ts
import { defineConfig } from "vite";
import vue from "@vitejs/plugin-vue";
import path from "path";
import UnoCSS from "unocss/vite";
import AutoImport from "unplugin-auto-import/vite";
import Components from "unplugin-vue-components/vite";
import { ElementPlusResolver } from "unplugin-vue-components/resolvers";
// https://vite.dev/config/
export default defineConfig({
resolve: {
alias: [{ find: "@", replacement: path.resolve(__dirname, "src") }]
},
plugins: [
vue(),
UnoCSS({
configFile: "./UnoCSS.config.ts"
}),
AutoImport({
resolvers: [ElementPlusResolver()]
}),
Components({
resolvers: [ElementPlusResolver()]
})
]
});2)测试
在 Dashboard.vue 中尝试引入一个 Element Plus 组件。

npm run dev 启动项目,可以看见页面能正常显示按钮,说明 Element Plus 组件已经自动按需导入了。

2. 自定义组件自动导入
一般自定义组件使用时,需要在页面用 import 引入,否则会报错。其实自定义组件也可以实现按需自定义导入,修改 vite.config.ts。
// vite.config.ts
import { defineConfig } from "vite";
import vue from "@vitejs/plugin-vue";
import path from "path";
import UnoCSS from "unocss/vite";
import AutoImport from "unplugin-auto-import/vite";
import Components from "unplugin-vue-components/vite";
import { ElementPlusResolver } from "unplugin-vue-components/resolvers";
// https://vite.dev/config/
export default defineConfig({
resolve: {
alias: [{ find: "@", replacement: path.resolve(__dirname, "src") }]
},
plugins: [
vue(),
UnoCSS({
configFile: "./UnoCSS.config.ts"
}),
AutoImport({
resolvers: [ElementPlusResolver()]
}),
Components({
// 解析Element Plus组件
resolvers: [ElementPlusResolver()],
// 所有的自定义组件可以自动加载
dirs: [
"src/components",
"src/layout/components",
"src/views/**/components",
"src/views/"
]
})
]
});这样,在页面中使用配置好的文件夹下的组件时,就会自动导入,如下图,可以在 Dashboard.vue 中直接使用 Test 组件,不需要 import。

页面效果如下

自动生成的配置文件
在配置自动导入过程中,会自动生成两个文件 auto-imports.d.ts 和 components.d.ts。
auto-imports.d.ts
- 作用:这个文件通常由一些自动导入插件生成,如
unplugin-auto-import。它包含了项目中使用的库、框架或自定义工具函数的类型声明,使得开发者可以在项目中直接使用这些功能,而无需在每个文件顶部手动编写 import 语句。- 内容:文件中通常会声明全局性的类型、函数、组件等,例如 Vue Composition API 的 ref, reactive, onMounted 等,或者是其他库的 API。
- 生成方式:通过配置自动导入插件的选项,指定生成的类型声明文件的路径。
components.d.ts
- 作用:这个文件通常用于 Vue.js 项目,由 Vue 的构建工具链(如 Vite 或 Vue CLI)自动生成。它包含了项目中所有 Vue 组件的类型声明,使得开发者可以在模板中直接使用这些组件,而无需导入。
- 内容:文件中会为每个 Vue 组件生成一个类型声明,这些声明告诉 TypeScript 编译器这些组件的存在以及它们的 props、slots 等。
- 生成方式:在 Vue 项目中,通常是通过
vue-loader或其他相关的 Vue 插件自动检测项目中的组件,并生成相应的类型声明。


1. 配置
在 vite 中使用 unplugin-auto-import 插件,会自动扫描代码,在需要的地方插入必要的导入语句。通常用于自动导入某些库或模块中的 API。
如图,在 vite.config.ts 中增加配置

在配置完成后,您就可以在项目中直接使用 vue、vue-router 和 pinia 的 API,而无需手动导入它们。例如,可以直接使用 ref、computed 等 Vue 的响应式 API,而无需在文件顶部写 import { ref, computed } from 'vue'。

如上图,虽然现在不需要在文件顶部通过 import 导入 vue 等的 api,但是 eslint 会飘红报错,这需要在 vite.config.ts 中修改一个 eslint 的配置。

eslintrc: { enabled: false } // 给eslint生产的配置 只需要一次,配置后,会生成一个 .eslintrc-auto-import.json 文件
在 eslint.config.js 中引入
// eslint.config.js
import globals from "globals";
import pluginJs from "@eslint/js"; // 推荐的js规范
import tseslint from "typescript-eslint"; // 推荐的ts规范
import pluginVue from "eslint-plugin-vue"; // 推荐的vue的规范
import prettierRecommended from "eslint-plugin-prettier/recommended";
import { createRequire } from "module";
const require = createRequire(import.meta.url);
const autoImport = require("./.eslintrc-auto-import.json");
export default [
{ files: ["**/*.{js,mjs,cjs,ts,vue}"] },
{
languageOptions: {
globals: {
...globals.browser,
...globals.node,
...autoImport.globals
}
}
},
pluginJs.configs.recommended,
...tseslint.configs.recommended,
...pluginVue.configs["flat/essential"],
{
files: ["**/*.vue"],
languageOptions: { parserOptions: { parser: tseslint.parser } }
},
{
// 自定义规则,根据需要增加 eslint 主要是校验代码规范 prettier 格式化代码的
rules: {
"no-console": "warn",
"vue/multi-word-component-names": "off"
}
},
prettierRecommended // 覆盖掉eslint的规范
];修改 tsconfig.app.json 的 types
// tsconfig.app.json
{
"extends": "@vue/tsconfig/tsconfig.dom.json",
"compilerOptions": {
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
/* Linting */
"strict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noFallthroughCasesInSwitch": true,
"noUncheckedSideEffectImports": true,
"baseUrl": ".",
"paths": {
"@/*": ["src/*"]
},
"types": ["element-plus/global", "./auto-imports.d.ts", "./components.d.ts"]
},
"include": ["src/**/*.ts", "src/**/*.tsx", "src/**/*.vue"]
}这样,页面中就不会报错了。

2. 服务型组件自动导入配置
服务型组件是指那些不依赖于传统 DOM 挂载点的组件,它们通常提供全局级别的功能,如消息提示、通知、确认对话框等。以下是一些 Element Plus 中的服务型组件:
- ElMessage:用于显示全局消息提示,支持成功、警告、消息和错误等不同类型的提示。它可以直接通过
this.$message或 ElMessage 方法调用,无需在模板中插入任何标签。 - ElNotification:用于显示全局通知,它类似于消息提示,但是通常用于显示更复杂或需要用户进行交互的通知。可以通过
this.$notification或 ElNotification 方法调用。 - ElMessageBox:提供模态对话框功能,用于显示警告、确认消息或收集用户输入。它可以通过
this.$msgbox,this.$alert,this.$confirm或 ElMessageBox 方法来调用。 - ElLoading:用于全局或局部显示加载状态。可以通过过
this.$loading或ElLoading.service方法来调用。
由于服务型组件的特点,上文中组件自动导入配置就无法生效,使用时需要单独引入。

为了使服务型组件也可以自动导入,这里需要单独配置。
在 src 下新建 plugins 文件夹,新建文件 element.ts
// element.ts
import type { App } from "vue";
import { ElMessage, ElNotification, ElMessageBox } from "element-plus";
export default (app: App) => {
// 都放到组件的实例上了
app.config.globalProperties.$message = ElMessage;
app.config.globalProperties.$notify = ElNotification;
app.config.globalProperties.$confirm = ElMessageBox.confirm;
app.config.globalProperties.$alert = ElMessageBox.alert;
app.config.globalProperties.$prompt = ElMessageBox.prompt;
};
export type Size = "default" | "small" | "large";在 main.ts 中引入 element.ts
// main.ts
import { createApp } from "vue";
import App from "./App.vue";
import router from "./router";
import "normalize.css/normalize.css";
import { createPinia } from "pinia";
import element from "./plugins/element";
// import ElementPlus from "element-plus";
// import "element-plus/dist/index.css";
import "@/style/index.scss";
import "uno.css";
const app = createApp(App);
const pinia = createPinia();
app.use(router);
app.use(pinia);
app.use(element);
// app.use(ElementPlus);
app.mount("#app");这样,就可以在页面中通过实例直接使用了

页面中点击按钮,可以发现消息功能正常,但是样式还存在问题。

解决样式问题,需要通过 pnpm 安装插件 unplugin-element-plus 来自动导入样式
pnpm i unplugin-element-plus在 vite.config.ts 中引入。

import ElementPlus from "unplugin-element-plus/vite";
export default defineConfig({
// ...
plugins: [
// ...
ElementPlus({}) // 导入样式不需要引入
]
});配置完成后,页面就能正常显示了,样式都是按需加载的。

第五章:图标组件
图标组件是前端开发中常用的元素,常见开发方式有三种:图片图标、字体图标和 SVG 图标。
- 图片图标:将图标存为 PNG 等格式,用
<img>标签或 CSS background - image 显示。优点是简单易用、兼容性好;缺点是不能无损缩放、文件大、难动态改颜色。 - 字体图标:把图标当字体处理,每个图标对应字体字符。可通过设置字体属性控制显示,如 Font Awesome 库,引入 CSS 文件后即可使用。优点是易改大小、颜色且不失真,文件小加载快;缺点是只能单色,更新需重新生成字体文件。
- SVG 图标:基于 XML 语法,可无损缩放、支持丰富效果。可内联 SVG 代码到 HTML,也能在前端框架封装成组件。优点是支持多色、无损缩放、可做动画交互;缺点是代码量大、旧版浏览器可能有兼容问题。
鉴于前文所述,本项目已完成支持图标的 UnoCSS 安装。UnoCSS 图标优势显著,灵活性高,支持按需引入,既能减小项目体积以加快加载速度,又能在运行时借助 CSS 类动态调整样式;集成便捷,以纯 CSS 实现,无额外 JavaScript 运行时负担,还能与 UnoCSS 原子化框架完美融合;视觉效果佳,基于 SVG 可无损缩放,且能呈现多色图标满足复杂设计;自定义能力强,允许用户添加自定义 SVG 图标并通过自定义 CSS 类打造独特风格。综合这些优点,我们决定采用 UnoCSS 开展图标配置工作。
本项目采用 UnoCSS 配置图标,同时使用 Iconify 作为图标数据源。
1. 安装插件
通过 pnpm 安装插件 @unocss/preset-icons 和 @iconify-json/ant-design。
pnpm i @unocss/preset-icons @iconify-json/ant-design -D
@unocss/preset-icons是 UnoCSS 的预设,用于在 UnoCSS 框架里集成图标支持。它支持多种图标源,借助简单的 CSS 类名,如i-{图标集前缀}-{图标名称},就能快速在项目中使用图标。还可自定义配置图标样式,如调整大小、颜色等。
@iconify-json/ant-design是包含 Ant Design 图标集的 JSON 数据包。Ant Design 图标丰富美观,该包将图标以 JSON 格式管理。它能与 Iconify 无缝集成,方便在不同项目和框架中使用 Ant Design 图标。
在 uno.config.ts 中修改 uno 配置
// uno.config.ts
import { defineConfig } from "unocss";
import presetUno from "@unocss/preset-uno";
import presetAttributify from "@unocss/preset-attributify";
import transformDirective from "@unocss/transformer-directives";
import presetIcons from "@unocss/preset-icons"; // unocss图标预设会自动查找依赖的图标库, 在此前我们安装了 ant-design, 在项目中就可以直接通过类名来使用了
export default defineConfig({
presets: [presetAttributify(), presetUno(), presetIcons()],
transformers: [transformDirective()],
});2. 页面图标使用
去 Iconify 官网上,搜索我们安装的 ant-design 图标库,点击想要使用的图标,复制 Icon name (如 ant-design:account-book-filled)备用。

在页面中通过 i-{Icon name} 即可使用,如下:


3. 图标原子类快捷方式
在使用 UnoCSS 集成图标时,若要为图标添加可复用样式,配置 UnoCSS 的 shortcuts 功能会是极为有效的解决方案。shortcuts 具备强大的自定义能力,支持用户定义原子化 CSS 类名的组合。用户只需创建一个涵盖图标样式相关原子类的快捷方式,后续在项目中就能直接调用该快捷类名,无需反复编写多个原子类,显著提升开发效率,让代码更加简洁易读。
比如,原本的写法如下,多个图标就要重复写多个同样的样式名称:

在 uno.config.ts 中配置 shortcuts: [["icon", "inline-block w-1em h-1em align-middle text-current"]],其中,icon 就是我们自定义的快捷类名,它将可复用的图标样式原子类组合到一起。
// uno.config.ts
import { defineConfig } from "unocss";
import presetAttributify from "@unocss/preset-attributify";
import presetUno from "@unocss/preset-uno";
import transformDirective from "@unocss/transformer-directives";
import presetIcons from "@unocss/preset-icons";
export default defineConfig({
presets: [presetAttributify(), presetUno(), presetIcons()],
transformers: [transformDirective()],
shortcuts: [["icon", "inline-block w-1em h-1em align-middle text-current"]]
});配置好后,可以在页面中使用快捷类名来定义统一的图标样式。

页面效果如下,F12 可以看到样式已经添加上。

4. 图标组件 svg-icon 封装
上文中的使用方式,如果我们想要使用其他图标资源,就需要手动下载相应的图标资源库,这样不仅容易造成文件冗余,使用过程繁琐,而且加载的图标还可能出现锯齿等显示问题。为解决这些痛点,实现以组件形式在 Vue 项目中渲染图标,可安装插件 @iconify/vue。@iconify/vue 是用于 Vue 项目集成 Iconify 图标功能的库,优势显著:
- 资源丰富调用统一:整合超 150 个图标集,像 Material Design Icons、Font Awesome、Ant Design Icons 等,超 20 万个图标,满足多样需求。且无论用哪个图标集,都能用统一语法引用,如
<Icon icon="mdi:home" />,降低学习成本、提升开发效率。 - 渲染质量高:基于 SVG 渲染图标,相比传统图标字体,在任何尺寸下都清晰锐利,无模糊锯齿,视觉效果好。同时,因 SVG 支持多色,能轻松实现多色图标显示,满足复杂设计需求。
- 资源管理高效:支持按需加载,组件会根据实际使用情况,自动从 Iconify API 加载所需图标数据,无需打包所有图标,减小项目体积、加快页面加载。图标库更新时,也无需手动更新文件,组件自动获取最新数据。
- 框架集成性佳:专为 Vue 设计,与 Vue 生态无缝融合,可方便地在组件中使用。还能结合 Vue 响应式特性,实现动态图标切换。此外,支持与 Vue 样式绑定机制结合,动态改变图标大小、颜色、旋转等样式。
- 社区文档完善:Iconify 有活跃社区,开发者可分享经验、交流问题。同时提供详细文档与示例代码,初学者也能快速上手,降低使用门槛。
通过 pnpm 安装插件 @iconify/vue
pnpm add @iconify/vue -D在 src 下新建文件夹 utils,新建文件 validate.ts,新增判断是否外部链接的方法
// validate.ts
// 是否外部链接
export const isExternal = (path: string): boolean => {
return /https?/.test(path);
};在 src/components 文件夹下新建 SvgIcon 文件夹,新建 index.vue 文件,封装图标组件如下:
<!-- SvgIcon/index.vue -->
<template>
<IconifyIcon
:class="svgClass"
:icon="iconName"
v-if="!isExt"
v-bind="$attrs"
></IconifyIcon>
<div
v-else
:style="styleExternalIcon"
:class="svgClass"
bg-current
v-bind="$attrs"
></div>
</template>
<script setup lang="ts">
import { isExternal } from "@/utils/validate";
import { Icon as IconifyIcon } from "@iconify/vue";
const { iconName, customClass } = defineProps({
iconName: {
type: String,
default: ""
},
customClass: {
type: String,
default: ""
}
});
const isExt = computed(() => isExternal(iconName));
// class="customClass + icon"
// 组合成的类名
const svgClass = computed(() => (customClass ? `icon ${customClass}` : "icon"));
// 通过 mask 渲染 svg 图标, 兼容性不好, 可以通过请求svg的方式来渲染
const styleExternalIcon = computed(() => ({
mask: `url(${iconName}) no-repeat 50% 50%`,
"-webkit-mask": `url(${iconName}) no-repeat 50% 50%`,
"mask-size": "cover"
}));
</script>
<!-- 在实现图标的时候, 尽量采用svg, 不要采用font图标, font图标放大时会出现锯齿等问题 -->该图标组件主要功能是根据传入的图标名称动态渲染图标,支持使用 @iconify/vue 库渲染图标以及外部 svg 图标。该组件接收 iconName 和 customClass 两个属性。iconName 表示要渲染的图标名称,customClass 是可选的自定义类名。组件会根据 iconName 是否为外部链接来决定渲染方式:
- 若 iconName 不是外部链接,使用
@iconify/vue库中的IconifyIcon组件来渲染图标,并应用 svgClass 作为样式类,同时将其他未定义的属性透传。 - 若 iconName 是外部链接,渲染一个 div 元素,通过 mask 和
-webkit-maskCSS 属性使用外部 SVG 图标,同样应用 svgClass 作为样式类,并透传其他属性。
5. svg-icon 组件使用
在页面中使用 svg-icon 组件,第一个组件使用本地图标,第二个组件使用外部 SVG 图标。
<!-- Dashboard/index.vue -->
<template>
<div i-ant-design:account-book-filled icon></div>
<div i-ant-design:aim-outlined icon></div>
<svg-icon
icon-name="material-symbols-light:18-up-rating-outline-rounded"
></svg-icon>
<svg-icon
icon-name="https://zishui.oss-cn-beijing.aliyuncs.com/BugFilled.svg"
:custom-class="val"
@click="handle"
></svg-icon>
</template>
<script lang="ts" setup>
const { proxy } = getCurrentInstance()!;
const handle = () => {
proxy?.$message("This is a message.");
};
const val = ref("text-blue");
setTimeout(() => {
val.value = "text-red";
}, 1000);
</script>页面效果如下,控制台可以看到渲染方式从之前的base64变成了svg形式。与 Base64 图标相比,svg 图标优势突出。视觉上,svg 基于矢量可无损缩放,在各分辨率设备都清晰,还能实现多色与复杂图形;Base64 缩放易失真。文件性能方面,svg 文件小,可被浏览器有效缓存,减少加载时间;Base64 编码后体积增加,缓存不便。开发维护上,svg 是文本格式,可直接编辑,便于复用和扩展;Base64 修改繁琐。另外,svg 中的文本利于 SEO,Base64 则无此优势。