基础项目技术方案回顾
本节目标
回顾 Starter 模板项目中已集成的技术方案,理解每个方案的作用和配置位置。对于直接跳到本章节学习的同学,这是一个快速了解项目基础的补充说明。
项目目录结构
admin-vue3/
├── src/
│ ├── components/ # 组件目录(自动注册)
│ │ └── MyButton.vue # 任意 .vue 文件自动全局注册
│ ├── pages/ # 页面目录(基于文件的路由)
│ │ ├── index.vue # → /
│ │ └── about.vue # → /about
│ ├── layouts/ # 布局系统
│ │ └── default.vue # 默认布局
│ ├── composables/ # 组合式函数
│ ├── stores/ # Pinia 状态管理
│ ├── assets/ # 静态资源
│ └── App.vue # 根组件
├── public/ # 公共资源
├── vite.config.ts # Vite 配置(核心)
├── tsconfig.json # TypeScript 配置
├── uno.config.ts # UnoCSS 配置
└── package.json # 项目依赖
text
已集成的技术方案一览
| 技术方案 | 包名 | 作用 |
|---|---|---|
| 自动组件注册 | unplugin-vue-components | components/ 下的组件自动全局注册 |
| 基于文件的路由 | unplugin-vue-router / vite-plugin-pages | pages/ 下的 .vue 文件自动生成路由 |
| 布局系统 | vite-plugin-vue-layouts | layouts/ 下的布局组件配合页面使用 |
| 状态管理 | pinia | Vue3 官方推荐的状态管理 |
| 原子化 CSS | unocss | 类似 Tailwind 的原子化 CSS 方案 |
| 自动 API 导入 | unplugin-auto-import | Vue3 核心 API 无需手动 import |
| Git Hooks | husky + lint-staged | 提交前自动执行代码检查 |
| 代码规范 | eslint + prettier | 统一代码风格 |
方案一:自动组件注册
原理
unplugin-vue-components 扫描 components/ 目录,自动生成组件注册代码。无需手动 import 和 app.component()。
使用前后对比
<!-- 传统方式:手动导入 -->
<script setup>
import MyButton from '@/components/MyButton.vue'
import UserProfile from '@/components/UserProfile.vue'
</script>
<template>
<MyButton />
<UserProfile />
</template>
vue
<!-- 自动注册方式:直接使用 -->
<script setup>
// 无需 import,直接在 template 中使用
</script>
<template>
<MyButton />
<UserProfile />
</template>
vue
配置位置
// vite.config.ts
import Components from 'unplugin-vue-components/vite'
export default defineConfig({
plugins: [
Components({
// 指定组件目录
dirs: ['src/components'],
// 组件名称格式:PascalCase
directoryAsNamespace: false,
// 自动生成类型声明文件
dts: 'src/components.d.ts',
}),
],
})
ts
方案二:基于文件的路由
原理
vite-plugin-pages 扫描 pages/ 目录,根据文件路径自动生成 Vue Router 路由配置。
路由映射规则
| 文件路径 | 生成的路由 |
|---|---|
pages/index.vue | / |
pages/about.vue | /about |
pages/users/index.vue | /users |
pages/users/[id].vue | /users/:id(动态路由) |
pages/users/[...slug].vue | /users/:slug(.*)*(捕获所有路由) |
使用方式
<!-- App.vue -->
<template>
<RouterView />
</template>
vue
<!-- pages/index.vue -->
<template>
<div>首页内容</div>
</template>
vue
无需手动编写 router.ts 配置文件。
配置位置
// vite.config.ts
import Pages from 'vite-plugin-pages'
export default defineConfig({
plugins: [
Pages({
// 指定页面目录
dirs: ['src/pages'],
// 自动生成路由类型
routeBlockLang: 'yaml',
}),
],
})
ts
方案三:布局系统
原理
vite-plugin-vue-layouts 配合文件路由使用,允许在页面级别指定布局组件。
使用方式
<!-- layouts/default.vue -->
<template>
<div class="layout">
<header>导航栏</header>
<main>
<slot /> <!-- 页面内容渲染在这里 -->
</main>
<footer>页脚</footer>
</div>
</template>
vue
<!-- pages/index.vue -->
<route lang="yaml">
meta:
layout: default
</route>
<template>
<div>首页内容(会被渲染在 default 布局的 slot 中)</div>
</template>
vue
方案四:自动 API 导入(unplugin-auto-import)
核心功能
unplugin-auto-import 让你在 <script setup> 中无需手动 import Vue 的核心 API。
使用前后对比
<!-- 传统方式 -->
<script setup>
import { ref, reactive, computed, onMounted, watch } from 'vue'
import { useRouter, useRoute } from 'vue-router'
import { useUserStore } from '@/stores/user'
const count = ref(0)
const router = useRouter()
</script>
vue
<!-- 自动导入方式 -->
<script setup>
// 无需 import,直接使用
const count = ref(0)
const router = useRouter()
const userStore = useUserStore()
onMounted(() => {
console.log('mounted')
})
</script>
vue
配置详解
// vite.config.ts
import AutoImport from 'unplugin-auto-import/vite'
export default defineConfig({
plugins: [
AutoImport({
imports: [
// Vue 核心库自动导入
'vue',
// Vue Router API 自动导入
'vue-router',
// VueUse 组合式函数集合
'@vueuse/core',
],
// 生成类型声明文件
dts: 'src/auto-imports.d.ts',
// 生成 eslint 配置
eslintrc: {
enabled: true,
},
}),
],
})
ts
支持自动导入的常用 API
| 来源 | 自动导入的 API |
|---|---|
vue | ref, reactive, computed, watch, onMounted, nextTick, toRef, toRefs 等 |
vue-router | useRouter, useRoute, onBeforeRouteLeave, onBeforeRouteUpdate |
@vueuse/core | useMouse, useWindowSize, useStorage, useFetch, useIntersectionObserver 等 |
TypeScript 类型支持
自动导入插件会生成 auto-imports.d.ts 文件,确保 TypeScript 能正确识别全局 API:
// src/auto-imports.d.ts(自动生成,不要手动编辑)
export {}
declare global {
const ref: typeof import('vue')['ref']
const reactive: typeof import('vue')['reactive']
const computed: typeof import('vue')['computed']
const onMounted: typeof import('vue')['onMounted']
// ...更多 API
}
ts
方案五:UnoCSS 原子化样式
原理
UnoCSS 是一个即时按需的原子化 CSS 引擎,类似 Tailwind CSS 但性能更高(比 Tailwind JIT 快 5 倍),可自定义预设规则。
使用示例
<template>
<!-- 直接在 class 中使用原子类 -->
<div class="flex items-center justify-between p-4 bg-blue-500 text-white rounded-lg shadow-md">
<span class="text-lg font-bold">管理后台</span>
<button class="px-4 py-2 bg-white text-blue-500 rounded hover:bg-gray-100">
登录
</button>
</div>
</template>
vue
常用预设
// uno.config.ts
import { defineConfig, presetUno, presetAttributify, presetIcons } from 'unocss'
export default defineConfig({
presets: [
// 默认预设(等价于 Tailwind CSS)
presetUno(),
// 属性化模式:允许 <div text="sm red" />
presetAttributify(),
// 图标支持:class="i-carbon-sun"
presetIcons({
scale: 1.2,
cdn: 'https://esm.sh/',
}),
],
})
ts
图标使用
UnoCSS 内置了 @iconify 图标集,可通过类名直接使用:
<template>
<!-- 使用 Iconify 图标 -->
<div class="i-carbon-sun text-2xl text-yellow-500" />
<div class="i-mdi-github text-xl" />
<div class="i-heroicons-solid-home text-lg" />
</template>
vue
方案六:Pinia 状态管理
基本用法
// stores/user.ts
import { defineStore } from 'pinia'
export const useUserStore = defineStore('user', () => {
// state
const userInfo = ref<{ name: string; role: string } | null>(null)
const isLoggedIn = computed(() => !!userInfo.value)
// actions
function login(name: string, role: string) {
userInfo.value = { name, role }
}
function logout() {
userInfo.value = null
}
return { userInfo, isLoggedIn, login, logout }
})
ts
<!-- 在组件中使用(配合 auto-import,无需 import) -->
<script setup>
const userStore = useUserStore()
// 响应式使用
const userName = computed(() => userStore.userInfo?.name ?? '未登录')
</script>
<template>
<div>{{ userName }}</div>
<button @click="userStore.login('Admin', 'admin')">登录</button>
</template>
vue
二开业务组件的概念
本项目不是从零构建基础 UI 组件库(如 Element Plus),而是基于现有 UI 库进行二次开发,构建业务类型的组件。
基础组件 vs 业务组件
| 维度 | 基础组件(Element Plus) | 业务组件(本项目) |
|---|---|---|
| 定位 | 通用 UI 原子组件 | 封装业务逻辑的复合组件 |
| 示例 | <el-button>, <el-input> | <UserSelector>, <PermissionButton> |
| 依赖 | 无外部 UI 库依赖 | 依赖 Element Plus 等基础库 |
| 复用性 | 跨项目通用 | 项目/业务域内复用 |
| 复杂度 | 单一职责,接口清晰 | 组合多个基础组件 + 业务逻辑 |
业务组件示例:自定义图标组件
UnoCSS 的 presetIcons 使用 Iconify 图标集(以 Material Design Icons 为主),但实际项目常需要自定义 SVG 图标。此时可创建业务组件:
<!-- components/SvgIcon.vue -->
<script setup>
interface Props {
name: string
size?: number | string
color?: string
}
const props = withDefaults(defineProps<Props>(), {
size: 16,
color: 'currentColor',
})
// 加载 SVG 图标模块
const modules = import.meta.glob('@/assets/icons/*.svg', { eager: true })
const iconPath = computed(() => {
const key = Object.keys(modules).find(k => k.includes(`${props.name}.svg`))
return key ? (modules[key] as any).default : ''
})
</script>
<template>
<svg
:width="size"
:height="size"
:fill="color"
class="inline-block"
>
<use :xlink:href="iconPath" />
</svg>
</template>
vue
vite.config.ts 完整配置概览
// vite.config.ts
import { defineConfig } from 'vite'
import Vue from '@vitejs/plugin-vue'
import AutoImport from 'unplugin-auto-import/vite'
import Components from 'unplugin-vue-components/vite'
import Pages from 'vite-plugin-pages'
import Layouts from 'vite-plugin-vue-layouts'
import Unocss from 'unocss/vite'
export default defineConfig({
plugins: [
Vue(),
// 自动 API 导入
AutoImport({
imports: ['vue', 'vue-router', '@vueuse/core', 'pinia'],
dts: 'src/auto-imports.d.ts',
}),
// 自动组件注册
Components({
dirs: ['src/components'],
dts: 'src/components.d.ts',
}),
// 基于文件的路由
Pages({
dirs: ['src/pages'],
}),
// 布局系统
Layouts({
layoutsDirs: ['src/layouts'],
}),
// UnoCSS
Unocss(),
],
})
ts
本节回顾
- 自动组件注册:
components/下的.vue文件自动全局注册,无需手动 import - 基于文件的路由:
pages/下的文件自动映射为路由,无需手动配置 router - 布局系统:通过 frontmatter 的
layout字段指定页面布局 - 自动 API 导入:
ref、reactive、computed等 Vue 核心 API 无需手动 import - UnoCSS:原子化 CSS 方案,内置 Iconify 图标支持
- Pinia:Vue3 官方状态管理方案
- 二开业务组件:在 Element Plus 等基础组件之上,封装带业务逻辑的复合组件
提示:对于不熟悉 Vue3 基础语法的同学,建议先学习 Vue3 官方文档,重点掌握 Composition API、
<script setup>语法和响应式系统。
↑