摘要:Vue 3作为前端主流框架,凭借Composition API、Teleport、Suspense等新特性,结合Vite的极速构建能力与Pinia的轻量状态管理,已成为企业级前端项目的首选技术栈。本文基于Vue 3.4、Vite 5、Pinia 2,详细讲解企业级前端项目的架构设计、路由管理、状态管理、组件封装、接口请求、权限控制、性能优化等核心知识点,结合实战场景(后台管理系统),附完整项目结构与代码案例,帮助前端开发者突破Vue 3进阶瓶颈,提升项目可维护性与开发效率,适合Vue开发者、前端架构师学习。
一、前言:Vue 3+Vite+Pinia的核心优势
相比Vue 2,Vue 3带来了全方位的升级:Composition API替代Options API,实现逻辑复用与代码组织的灵活性;Teleport实现组件跨DOM渲染,解决弹窗、模态框等场景的布局问题;Suspense支持异步组件加载,提升首屏加载体验;TypeScript深度集成,提供类型安全保障。
Vite作为新一代构建工具,相比Webpack,采用ES Module原生支持,实现极速冷启动、热更新,大幅提升开发效率;Pinia作为Vue官方推荐的状态管理工具,替代Vuex,简化状态管理逻辑,支持TypeScript,体积更小、API更简洁。本文聚焦企业级前端项目架构设计,帮助开发者搭建规范、可扩展的Vue 3项目。
二、核心基础:项目初始化与架构设计
2.1 项目初始化(Vite+Vue 3+TypeScript)
bash
# 1. 初始化Vue 3+TypeScript项目(Vite方式)
npm create vite@latest vue3-enterprise-demo -- --template vue-ts
# 2. 进入项目目录
cd vue3-enterprise-demo
# 3. 安装核心依赖
npm install pinia vue-router@4 axios element-plus @element-plus/icons-vue
npm install -D sass eslint prettier @types/node
# 4. 启动开发服务器
npm run dev
# 5. 构建生产环境
npm run build
2.2 企业级项目目录结构设计
text
vue3-enterprise-demo/
├── public/ # 静态资源(无需打包的图片、字体)
├── src/
│ ├── api/ # 接口请求封装(按模块划分)
│ │ ├── user.ts # 用户模块接口
│ │ ├── order.ts # 订单模块接口
│ │ └── index.ts # 接口请求工具封装
│ ├── assets/ # 静态资源(图片、样式、字体,需打包)
│ │ ├── images/ # 图片资源
│ │ └── styles/ # 全局样式(reset.scss、variables.scss)
│ ├── components/ # 组件(按功能划分)
│ │ ├── common/ # 通用组件(按钮、表单、弹窗等)
│ │ ├── layout/ # 布局组件(页面布局、侧边栏、顶部导航)
│ │ └── business/ # 业务组件(按业务模块划分)
│ ├── hooks/ # 自定义hooks(逻辑复用)
│ │ ├── useAuth.ts # 权限相关hooks
│ │ ├── useRequest.ts # 接口请求hooks
│ │ └── useForm.ts # 表单处理hooks
│ ├── router/ # 路由配置(路由定义、守卫)
│ │ ├── index.ts # 路由入口
│ │ └── routes.ts # 路由规则
│ ├── store/ # Pinia状态管理(按模块划分)
│ │ ├── modules/ # 模块状态(user、order等)
│ │ └── index.ts # Pinia入口
│ ├── types/ # TypeScript类型定义
│ │ ├── api.ts # 接口返回数据类型
│ │ ├── component.ts # 组件props类型
│ │ └── index.ts # 通用类型
│ ├── utils/ # 工具函数(权限、日期、格式化等)
│ ├── views/ # 页面组件(按业务模块划分)
│ │ ├── login/ # 登录页面
│ │ ├── dashboard/ # 仪表盘页面
│ │ ├── user/ # 用户管理页面
│ │ └── order/ # 订单管理页面
│ ├── App.vue # 根组件
│ ├── main.ts # 入口文件
│ └── vite-env.d.ts # 环境类型声明
├── .eslintrc.js # ESLint配置(代码规范)
├── .prettierrc.js # Prettier配置(代码格式化)
├── vite.config.ts # Vite配置
└── package.json # 依赖配置
三、实战模块:核心功能实战与架构落地
3.1 模块1:路由管理(Vue Router 4)
Vue Router 4是Vue 3的官方路由工具,支持Composition API、动态路由、路由守卫等功能,负责页面跳转与路由权限控制,是前端项目的核心组件。
typescript
// 1. 路由规则定义(src/router/routes.ts)
import { RouteRecordRaw } from 'vue-router';
// 路由规则(分公开路由与私有路由)
export const publicRoutes: RouteRecordRaw[] = [
{
path: '/login',
name: 'Login',
component: () => import('@/views/login/Login.vue'),
meta: { title: '登录' }
},
{
path: '/404',
name: '404',
component: () => import('@/views/error/404.vue'),
meta: { title: '页面不存在' }
}
];
export const privateRoutes: RouteRecordRaw[] = [
{
path: '/',
name: 'Layout',
component: () => import('@/components/layout/Layout.vue'),
redirect: '/dashboard',
children: [
{
path: 'dashboard',
name: 'Dashboard',
component: () => import('@/views/dashboard/Dashboard.vue'),
meta: { title: '仪表盘', icon: 'HomeFilled' }
},
{
path: 'user',
name: 'User',
component: () => import('@/views/user/UserList.vue'),
meta: { title: '用户管理', icon: 'UserFilled', permission: ['admin'] }
},
{
path: 'order',
name: 'Order',
component: () => import('@/views/order/OrderList.vue'),
meta: { title: '订单管理', icon: 'Order', permission: ['admin', 'operator'] }
}
]
}
];
// 2. 路由入口配置(src/router/index.ts)
import { createRouter, createWebHistory } from 'vue-router';
import { publicRoutes, privateRoutes } from './routes';
import { useUserStore } from '@/store/modules/user';
const router = createRouter({
history: createWebHistory(import.meta.env.BASE_URL),
routes: [...publicRoutes]
});
// 路由守卫(权限控制)
router.beforeEach((to, from, next) => {
// 设置页面标题
document.title = to.meta.title as string || 'Vue 3企业级项目';
const userStore = useUserStore();
const token = userStore.token;
// 未登录,只能访问公开路由
if (!token) {
if (to.path === '/login') {
next();
} else {
next('/login');
}
return;
}
// 已登录,访问登录页,跳转到仪表盘
if (token && to.path === '/login') {
next('/dashboard');
return;
}
// 权限校验(判断是否拥有访问该路由的权限)
if (to.meta.permission) {
const permissions = userStore.permissions;
const hasPermission = (to.meta.permission as string[]).some(perm => permissions.includes(perm));
if (hasPermission) {
next();
} else {
next('/404');
}
} else {
next();
}
});
export default router;
3.2 模块2:状态管理(Pinia)
Pinia替代Vuex,简化状态管理逻辑,支持模块化、TypeScript、热更新,无需手动注册,直接通过useStore调用,适合企业级项目的状态管理场景。
typescript
// 1. Pinia入口配置(src/store/index.ts)
import { createPinia } from 'pinia';
import { useUserStore } from './modules/user';
import { useOrderStore } from './modules/order';
const pinia = createPinia();
// 导出所有store,方便全局使用
export { pinia, useUserStore, useOrderStore };
// 2. 用户模块状态管理(src/store/modules/user.ts)
import { defineStore } from 'pinia';
import { loginApi, getUserInfoApi, logoutApi } from '@/api/user';
import type { LoginParams, UserInfo, UserState } from '@/types/api';
import { setToken, getToken, removeToken } from '@/utils/auth';
// 定义用户状态Store
export const useUserStore = defineStore('user', {
state: (): UserState => ({
token: getToken() || '', // 从本地存储获取Token
userInfo: {} as UserInfo,
permissions: [] as string[]
}),
actions: {
// 登录
async login(params: LoginParams) {
const res = await loginApi(params);
const { token } = res.data;
// 存储Token(本地存储+Pinia状态)
this.token = token;
setToken(token);
// 获取用户信息
await this.getUserInfo();
},
// 获取用户信息
async getUserInfo() {
const res = await getUserInfoApi();
this.userInfo = res.data.userInfo;
this.permissions = res.data.permissions;
},
// 退出登录
async logout() {
await logoutApi();
// 清除状态与本地存储
this.token = '';
this.userInfo = {} as UserInfo;
this.permissions = [];
removeToken();
}
},
// 持久化配置(可选,需安装pinia-plugin-persistedstate)
persist: {
key: 'user-store',
storage: localStorage,
paths: ['token', 'permissions'] // 只持久化token和permissions
}
});
// 3. 订单模块状态管理(src/store/modules/order.ts)
import { defineStore } from 'pinia';
import { getOrderListApi, getOrderDetailApi } from '@/api/order';
import type { OrderListParams, OrderList, OrderDetail } from '@/types/api';
export const useOrderStore = defineStore('order', {
state: () => ({
orderList: {} as OrderList,
orderDetail: {} as OrderDetail,
loading: false
}),
actions: {
// 获取订单列表
async getOrderList(params: OrderListParams) {
this.loading = true;
try {
const res = await getOrderListApi(params);
this.orderList = res.data;
} finally {
this.loading = false;
}
},
// 获取订单详情
async getOrderDetail(id: string) {
this.loading = true;
try {
const res = await getOrderDetailApi(id);
this.orderDetail = res.data;
} finally {
this.loading = false;
}
}
}
});
3.3 模块3:接口请求封装(Axios)
统一封装Axios,处理请求拦截、响应拦截、异常处理、Token携带等逻辑,提升接口请求的可维护性,避免重复代码。
typescript
// 1. 接口请求工具封装(src/api/index.ts)
import axios, { AxiosRequestConfig, AxiosResponse, AxiosError } from 'axios';
import { useUserStore } from '@/store/modules/user';
import { ElMessage } from 'element-plus';
// 创建Axios实例
const service = axios.create({
baseURL: import.meta.env.VITE_API_BASE_URL, // 环境变量配置接口地址
timeout: 5000,
headers: {
'Content-Type': 'application/json;charset=utf-8'
}
});
// 请求拦截器(添加Token)
service.interceptors.request.use(
(config: AxiosRequestConfig) => {
const userStore = useUserStore();
// 携带Token
if (userStore.token) {
config.headers.Authorization = `Bearer ${userStore.token}`;
}
return config;
},
(error: AxiosError) => {
ElMessage.error('请求发送失败,请稍后再试');
return Promise.reject(error);
}
);
// 响应拦截器(处理响应与异常)
service.interceptors.response.use(
(response: AxiosResponse) => {
const { code, message, data } = response.data;
// 接口请求成功(code=200)
if (code === 200) {
return response.data;
} else {
// 接口请求失败(非200状态码)
ElMessage.error(message || '接口请求失败');
return Promise.reject(new Error(message || '接口请求失败'));
}
},
(error: AxiosError) => {
const userStore = useUserStore();
// Token过期(401状态码)
if (error.response?.status === 401) {
ElMessage.error('登录已过期,请重新登录');
// 退出登录,跳转至登录页
userStore.logout();
window.location.href = '/login';
} else if (error.response?.status === 403) {
ElMessage.error('没有访问权限,请联系管理员');
} else {
ElMessage.error('网络异常,请稍后再试');
}
return Promise.reject(error);
}
);
// 封装请求方法(get、post、put、delete)
export const request = {
get<T>(url: string, params?: Record<string, any>): Promise<T> {
return service.get(url, { params });
},
post<T>(url: string, data?: Record<string, any>): Promise<T> {
return service.post(url, data);
},
put<T>(url: string, data?: Record<string, any>): Promise<T> {
return service.put(url, data);
},
delete<T>(url: string, params?: Record<string, any>): Promise<T> {
return service.delete(url, { params });
}
};
// 2. 用户模块接口封装(src/api/user.ts)
import { request } from './index';
import type { LoginParams, LoginResponse, UserInfoResponse } from '@/types/api';
// 登录接口
export const loginApi = (params: LoginParams) => {
return request.post<LoginResponse>('/api/login', params);
};
// 获取用户信息接口
export const getUserInfoApi = () => {
return request.get<UserInfoResponse>('/api/user/info');
};
// 退出登录接口
export const logoutApi = () => {
return request.post('/api/logout');
};
3.4 模块4:组件封装与自定义Hooks
组件封装遵循"单一职责"原则,将通用逻辑与业务逻辑分离,结合自定义Hooks实现逻辑复用,提升组件可维护性与复用性。
vue
// 1. 通用按钮组件封装(src/components/common/MyButton.vue)
<template>
<el-button
:type="type"
:size="size"
:loading="loading"
:disabled="disabled"
@click="handleClick"
:class="customClass"
>
<slot></slot>
</el-button>
</template>
<script setup lang="ts">
import { defineProps, defineEmits } from 'vue';
import type { ButtonProps } from 'element-plus';
// 定义组件Props
const props = defineProps<{
type?: ButtonProps['type'];
size?: ButtonProps['size'];
loading?: boolean;
disabled?: boolean;
customClass?: string;
}>({
type: {
type: String,
default: 'primary'
},
size: {
type: String,
default: 'default'
},
loading: {
type: Boolean,
default: false
},
disabled: {
type: Boolean,
default: false
},
customClass: {
type: String,
default: ''
}
});
// 定义组件事件
const emit = defineEmits(['click']);
// 点击事件处理
const handleClick = () => {
emit('click');
};
</script>
// 2. 自定义表单处理Hooks(src/hooks/useForm.ts)
import { ref, reactive, toRefs, UnwrapRef } from 'vue';
// 表单类型定义(泛型)
type FormType = Record<string, any>;
// 表单校验规则类型
type RuleType = Record<string, any[]>;
// 自定义Form Hooks
export const useForm = <T extends FormType>(initialForm: T, rules?: RuleType) => {
// 表单数据
const form = reactive<UnwrapRef<T>>(initialForm);
// 表单校验状态
const formRef = ref<{ validate: () => Promise<boolean>; resetFields: () => void } | null>(null);
// 表单加载状态
const loading = ref(false);
// 重置表单
const resetForm = () => {
formRef.value?.resetFields();
};
// 校验表单
const validateForm = async () => {
if (!formRef.value) return false;
return await formRef.value.validate();
};
// 提交表单(回调函数)
const submitForm = async (callback: (formData: T) => Promise<void>) => {
const isValid = await validateForm();
if (!isValid) return;
loading.value = true;
try {
await callback({ ...form });
} finally {
loading.value = false;
}
};
return {
...toRefs(form),
form,
formRef,
loading,
resetForm,
validateForm,
submitForm
};
};
// 3. 表单组件使用示例(src/views/user/AddUser.vue)
<template>
<el-dialog title="新增用户" v-model="visible" width="500px">
<el-form
ref="formRef"
:model="form"
:rules="rules"
label-width="100px"
>
<el-form-item label="用户名" prop="username">
<el-input v-model="form.username" placeholder="请输入用户名" />
</el-form-item>
<el-form-item label="密码" prop="password">
<el-input type="password" v-model="form.password" placeholder="请输入密码" />
</el-form-item>
<el-form-item label="手机号" prop="phone">
<el-input v-model="form.phone" placeholder="请输入手机号" />
</el-form-item>
</el-form>
<template #footer>
<my-button @click="visible = false" type="default">取消</my-button>
<my-button @click="handleSubmit" type="primary" :loading="loading">确定</my-button>
</template>
</el-dialog>
</template>
<script setup lang="ts">
import { ref } from 'vue';
import { useForm } from '@/hooks/useForm';
import { addUserApi } from '@/api/user';
import MyButton from '@/components/common/MyButton.vue';
import { ElMessage } from 'element-plus';
const visible = ref(false);
// 初始化表单
const { form, formRef, loading, submitForm } = useForm({
username: '',
password: '',
phone: ''
}, {
username: [
{ required: true, message: '请输入用户名', trigger: 'blur' },
{ min: 3, max: 20, message: '用户名长度在3-20之间', trigger: 'blur' }
],
password: [
{ required: true, message: '请输入密码', trigger: 'blur' },
{ pattern: /^[a-zA-Z0-9]{6,16}$/, message: '密码必须是6-16位字母或数字', trigger: 'blur' }
],
phone: [
{ required: true, message: '请输入手机号', trigger: 'blur' },
{ pattern: /^1[3-9]\d{9}$/, message: '手机号格式不正确', trigger: 'blur' }
]
});
// 提交表单
const handleSubmit = async () => {
await submitForm(async (formData) => {
await addUserApi(formData);
ElMessage.success('新增用户成功');
visible.value = false;
// 触发父组件刷新列表
emit('refresh');
});
};
// 定义事件
const emit = defineEmits(['refresh']);
// 暴露方法,供父组件调用
defineExpose({
open: () => {
visible.value = true;
formRef.value?.resetFields();
}
});
</script>
四、性能优化与最佳实践
4.1 核心性能优化技巧
-
组件懒加载:使用Vue 3的defineAsyncComponent或路由懒加载,减小首屏加载体积,提升首屏加载速度。
-
虚拟列表:对于大量数据列表(如1000条以上),使用vue-virtual-scroller实现虚拟列表,只渲染可视区域内容,减少DOM渲染压力。
-
缓存组件:使用KeepAlive缓存频繁切换的组件(如标签页),避免重复渲染,提升切换体验。
-
图片优化:使用懒加载(vue-lazyload)、WebP格式图片,减小图片体积;对于静态图片,使用Vite的import.meta.glob批量导入。
-
代码分割:通过Vite的splitChunks配置,将第三方依赖与业务代码分割,实现按需加载。
4.2 最佳实践
-
代码规范:使用ESLint+Prettier统一代码风格,避免代码混乱;使用TypeScript明确类型定义,减少运行时错误。
-
状态管理:按业务模块划分Pinia Store,避免单一Store过于庞大;对于组件内部状态,使用ref/reactive,无需放入Pinia。
-
组件封装:通用组件与业务组件分离,通用组件抽象成独立模块,便于复用;业务组件按业务模块划分,提升可维护性。
-
接口管理:按业务模块封装接口,统一处理异常与Token携带;使用环境变量区分开发、测试、生产环境的接口地址。
五、总结与延伸
本文基于Vue 3+Vite+Pinia,详细讲解了企业级前端项目的架构设计、核心功能实战与性能优化技巧,覆盖路由管理、状态管理、接口封装、组件设计等核心知识点,提供了完整的项目结构与代码案例,帮助开发者搭建规范、可扩展的前端项目。
延伸学习:可深入研究Vue 3的Composition API原理、Suspense异步组件实战、Vite插件开发、前端工程化(CI/CD)、跨端开发(Vue 3+Uniapp),以及前端性能监控(Sentry),进一步提升前端开发能力与项目质量。