Vue3 + Vite5 企业级前端架构设计实践
一、项目目录与分层
企业级 Vue 项目推荐按功能模块组织,不采用 MVC 风格的 views/components/stores/api 大类分层,避免单目录上千文件不可维护。
src/
├── views/ # 路由页面(按业务模块分子目录)
│ ├── auth/ # 登录注册
│ ├── dashboard/ # 仪表盘
│ └── settings/ # 系统设置
├── components/ # 通用组件(与业务解耦)
│ ├── common/ # Button / Input / Table 等基础组件
│ └── business/ # 可复用业务组件(带类型,无 store 依赖)
├── modules/ # 业务模块(包含本模块的 api/store/types/views)
├── composables/ # 组合式函数(useXxx 风格)
├── stores/ # Pinia 全局 store
├── api/ # 后端接口封装(按领域分文件)
├── utils/ # 纯函数工具
├── types/ # 全局类型声明
├── router/ # 路由(分文件按模块合并)
└── assets/ # 静态资源
二、组件设计原则
- 单一职责:单组件不超过 300 行,超出就拆。
- Props 明确:必选/可选/默认值/类型都显式声明。
- 父子单向数据流:子组件 emit 事件,父组件处理状态;兄弟通过 Pinia。
- 通用组件无业务逻辑:可作为独立 npm 包存在。
三、Axios 封装与错误处理
// utils/request.ts
import axios, { AxiosError } from 'axios'
const http = axios.create({
baseURL: import.meta.env.VITE_API_BASE,
timeout: 15000,
})
// 请求拦截:注入 Token
http.interceptors.request.use(config => {
const token = localStorage.getItem('token')
if (token) config.headers.Authorization = `Bearer ${token}`
return config
})
// 响应拦截:统一解析 + 错误文案
http.interceptors.response.use(
(res) => {
const body = res.data
if (body?.code !== 200 && body?.code !== 0) {
throw new BusinessError(body?.code, body?.message || '请求失败')
}
return body?.data ?? body
},
(err: AxiosError) => {
const status = err.response?.status
if (status === 401) {
localStorage.removeItem('token')
location.href = '/login'
}
const msg = (err.response?.data as any)?.message || err.message || '网络异常'
ElMessage.error(msg)
return Promise.reject(err)
}
)
export default http
四、构建优化(Vite5 + rollup)
- 拆包策略:按 vendor / element-plus / echarts / vue-ecosystem 四组拆
- 预渲染:营销 / 博客 / 案例页用 vite-ssg,首屏速度 1-2s
- 图片:WebP + 懒加载 + 雪碧图(小图)
- 依赖体积:lodash-es 代替 lodash,按需引入 ECharts。
五、性能监控指标
| 指标 | 目标值 | 说明 |
|---|---|---|
| LCP | < 2.5s | 最大内容绘制,页面主内容出现时间 |
| FID | < 100ms | 首次交互延迟,用户点击响应时间 |
| CLS | < 0.1 | 布局抖动,图片/字体缺尺寸最常见 |
| TTFB | < 600ms | 首字节,后端接口响应时间 |
六、常见问题(FAQ)
Q1: 开发环境接口跨域怎么处理?
A: 配置 vite.config.ts 的 server.proxy,本地把 /api 前缀代理到测试环境。上线前用 Nginx 同样的 /api 转发到后端。
Q2: 首屏加载慢(JS 体积 > 2MB)怎么优化?
A: ① 路由级 code-splitting;② Element Plus 按需 import;③ ECharts/大图表单独拆 chunk;④ 非必要依赖用 CDN + external。
Q3: Pinia 和 Props 传参的边界?
A: 跨 ≥2 个组件或路由级共享的状态放 Pinia;父子直接交互放 Props/emit。错误做法:所有状态全丢 Pinia。