一、项目初始化与目录结构设计
一个可维护、可扩展的项目结构是大型前端项目的基石。在启动项目时,花 30 分钟设计合理的目录结构,将节省未来数百小时的维护成本。
# 使用 create-vite 初始化
npm create vite@latest finboost-admin -- --template vue-ts
# 进入项目
cd finboost-admin
# 安装核心依赖
npm install vue-router@4 pinia axios element-plus
npm install -D @types/node sass unplugin-auto-import unplugin-vue-components
推荐的企业级目录结构:
finboost-admin/
├── .env # 环境变量(默认)
├── .env.development # 开发环境变量
├── .env.production # 生产环境变量
├── vite.config.ts # Vite 构建配置
├── tsconfig.json # TypeScript 配置
├── index.html # HTML 入口
│
├── public/ # 静态资源(不经过编译)
│ └── favicon.ico
│
└── src/
├── main.ts # 应用入口
├── App.vue # 根组件
│
├── api/ # API 接口层
│ ├── index.ts # Axios 实例封装
│ ├── modules/ # 按业务模块拆分
│ │ ├── auth.ts # 认证相关 API
│ │ ├── order.ts # 订单相关 API
│ │ └── user.ts # 用户相关 API
│ └── types.ts # API 类型定义
│
├── assets/ # 编译时资源
│ ├── styles/
│ │ ├── variables.scss # SCSS 变量
│ │ ├── mixins.scss # SCSS Mixin
│ │ └── global.scss # 全局样式
│ └── images/
│
├── components/ # 全局通用组件
│ ├── common/ # 基础组件(Button, Modal 等)
│ └── business/ # 业务组件
│
├── composables/ # 组合式函数(Hooks)
│ ├── useAuth.ts # 认证逻辑
│ ├── usePermission.ts # 权限控制
│ └── useTable.ts # 表格通用逻辑
│
├── directives/ # 自定义指令
│ └── permission.ts # v-permission 权限指令
│
├── layouts/ # 布局组件
│ ├── DefaultLayout.vue
│ └── AuthLayout.vue
│
├── router/ # 路由配置
│ ├── index.ts # 路由实例
│ ├── routes/ # 路由模块
│ │ ├── auth.ts
│ │ ├── dashboard.ts
│ │ └── settings.ts
│ └── guards.ts # 路由守卫
│
├── stores/ # Pinia 状态管理
│ ├── user.ts # 用户状态
│ ├── app.ts # 应用全局状态
│ └── permission.ts # 权限状态
│
├── types/ # 全局类型定义
│ ├── global.d.ts
│ └── api.d.ts
│
├── utils/ # 工具函数
│ ├── request.ts # HTTP 请求工具
│ ├── storage.ts # 本地存储封装
│ └── validator.ts # 表单验证
│
└── views/ # 页面组件(按模块)
├── auth/
│ └── Login.vue
├── dashboard/
│ └── Index.vue
├── orders/
│ ├── List.vue
│ └── Detail.vue
└── settings/
└── Index.vue
二、Vite 配置深度优化
// vite.config.ts
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import { resolve } from 'path'
import AutoImport from 'unplugin-auto-import/vite'
import Components from 'unplugin-vue-components/vite'
import { ElementPlusResolver } from 'unplugin-vue-components/resolvers'
export default defineConfig({
plugins: [
vue(),
AutoImport({
resolvers: [ElementPlusResolver()],
imports: ['vue', 'vue-router', 'pinia'],
dts: 'src/auto-imports.d.ts',
}),
Components({
resolvers: [ElementPlusResolver()],
dts: 'src/components.d.ts',
}),
],
resolve: {
alias: {
'@': resolve(__dirname, 'src'),
'@api': resolve(__dirname, 'src/api'),
'@views': resolve(__dirname, 'src/views'),
'@components': resolve(__dirname, 'src/components'),
},
},
css: {
preprocessorOptions: {
scss: {
additionalData: `@use "@/assets/styles/variables.scss" as *; @use "@/assets/styles/mixins.scss" as *;`,
},
},
},
server: {
port: 5173,
proxy: {
'/api': {
target: 'http://localhost:3004',
changeOrigin: true,
rewrite: (path) => path.replace(/^\/api/, ''),
},
},
},
build: {
target: 'es2020',
outDir: 'dist',
assetsDir: 'assets',
sourcemap: false,
// 代码分割策略
rollupOptions: {
output: {
manualChunks: {
'vue-vendor': ['vue', 'vue-router', 'pinia'],
'ui-vendor': ['element-plus'],
'chart-vendor': ['echarts', 'vue-echarts'],
},
},
},
// 压缩配置
minify: 'terser',
terserOptions: {
compress: {
drop_console: true,
drop_debugger: true,
},
},
// 分块大小警告阈值
chunkSizeWarningLimit: 1000,
},
})
三、Pinia 状态管理最佳实践
// stores/user.ts — 组合式 API 风格(推荐)
import { defineStore } from 'pinia'
import { ref, computed } from 'vue'
import { getUserInfo, login, logout } from '@/api/modules/auth'
import type { UserInfo } from '@/types/api'
import { storage } from '@/utils/storage'
export const useUserStore = defineStore('user', () => {
// ── State ──
const token = ref(storage.get('token') || '')
const userInfo = ref(null)
// ── Getters ──
const isLoggedIn = computed(() => !!token.value)
const userName = computed(() => userInfo.value?.name || '未登录')
const roles = computed(() => userInfo.value?.roles || [])
// ── Actions ──
async function doLogin(username: string, password: string) {
const res = await login({ username, password })
token.value = res.token
storage.set('token', res.token)
await fetchUserInfo()
}
async function fetchUserInfo() {
const res = await getUserInfo()
userInfo.value = res
}
async function doLogout() {
await logout()
token.value = ''
userInfo.value = null
storage.remove('token')
}
// 导出
return { token, userInfo, isLoggedIn, userName, roles, doLogin, fetchUserInfo, doLogout }
})
四、Vue Router 路由设计模式
// router/index.ts
import { createRouter, createWebHistory } from 'vue-router'
import type { RouteRecordRaw } from 'vue-router'
import { setupGuards } from './guards'
// 路由模块化拆分
const routes: RouteRecordRaw[] = [
{
path: '/',
component: () => import('@/layouts/DefaultLayout.vue'),
redirect: '/dashboard',
children: [
{
path: 'dashboard',
name: 'Dashboard',
component: () => import('@/views/dashboard/Index.vue'),
meta: { title: '仪表盘', icon: 'Dashboard', roles: ['admin', 'manager'] },
},
{
path: 'orders',
name: 'OrderList',
component: () => import('@/views/orders/List.vue'),
meta: { title: '订单管理', keepAlive: true },
},
],
},
{
path: '/login',
name: 'Login',
component: () => import('@/views/auth/Login.vue'),
meta: { title: '登录', noAuth: true },
},
{
path: '/:pathMatch(.*)*',
name: 'NotFound',
component: () => import('@/views/error/404.vue'),
},
]
const router = createRouter({
history: createWebHistory(),
routes,
scrollBehavior: () => ({ top: 0 }),
})
// 注册全局守卫
setupGuards(router)
export default router
五、构建优化与分包策略
经过上述配置优化后,生产构建包体积通常可缩减 40%-60%:
| 优化项 | 优化前 | 优化后 | 效果 |
|---|---|---|---|
| 主包体积 | 850 KB | 280 KB | ↓ 67% |
| 整体构建时间 | 45s | 18s | ↓ 60% |
| 首屏加载时间 | 3.2s | 0.8s | ↓ 75% |
| Lighthouse 评分 | 62 | 94 | ↑ 52% |
💡 工程化核心原则
坚持单向依赖原则:View 依赖 Composable → Composable 依赖 Store → Store 依赖 Service。严禁跨层引用和反向依赖。将通用逻辑抽取为 Composable,避免组件间代码复制。路由配置按业务模块拆分,使用动态 import 实现路由级代码分割。