Vue 3 + Vite 项目实战:从 0 搭建现代化前端工程
> 标签: vue vite frontend 工程化 javascript
> 作者: @vue_lover
> 发布时间: 2026-06-12
项目概述
本文将带你从零搭建一个基于 Vue 3 + Vite 的现代化前端项目,包含:
- Vue 3 Composition API
- Vite 构建工具
- TypeScript 支持
- 路由管理(Vue Router)
- 状态管理(Pinia)
- UI 组件库(Element Plus)
- 代码规范(ESLint + Prettier)
环境准备
# 检查 Node.js 版本(建议 18+)
node -v
# 使用 pnpm(推荐)
npm install -g pnpm
第一步:项目初始化
# 使用 Vite 创建项目
pnpm create vite@latest my-vue-app --template vue-ts
# 进入项目
cd my-vue-app
# 安装依赖
pnpm install
# 启动开发服务器
pnpm dev
第二步:安装核心依赖
# 路由
pnpm add vue-router@4
# 状态管理
pnpm add pinia
# UI 组件库
pnpm add element-plus
# 图标
pnpm add @element-plus/icons-vue
# HTTP 请求
pnpm add axios
# 工具库
pnpm add lodash-es dayjs
第三步:项目结构规划
my-vue-app/
├── public/ # 静态资源
├── src/
│ ├── api/ # API 接口
│ ├── assets/ # 样式、图片
│ ├── components/ # 公共组件
│ ├── composables/ # 组合式函数
│ ├── layouts/ # 布局组件
│ ├── router/ # 路由配置
│ ├── stores/ # Pinia 状态
│ ├── utils/ # 工具函数
│ ├── views/ # 页面组件
│ ├── App.vue # 根组件
│ └── main.ts # 入口文件
├── .env # 环境变量
├── .env.production # 生产环境
├── vite.config.ts # Vite 配置
├── tsconfig.json # TS 配置
└── package.json
第四步:核心配置
4.1 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']
}),
Components({
resolvers: [ElementPlusResolver()]
})
],
resolve: {
alias: {
'@': resolve(__dirname, 'src')
}
},
server: {
port: 3000,
proxy: {
'/api': {
target: 'http://localhost:8080',
changeOrigin: true,
rewrite: (path) => path.replace(/^\/api/, '')
}
}
},
build: {
outDir: 'dist',
sourcemap: true,
rollupOptions: {
output: {
manualChunks: {
'element-plus': ['element-plus'],
'vue-vendor': ['vue', 'vue-router', 'pinia']
}
}
}
}
})
4.2 路由配置
// src/router/index.ts
import { createRouter, createWebHistory } from 'vue-router'
import { useUserStore } from '@/stores/user'
const routes = [
{
path: '/',
component: () => import('@/layouts/MainLayout.vue'),
children: [
{
path: '',
name: 'Home',
component: () => import('@/views/Home.vue')
},
{
path: 'about',
name: 'About',
component: () => import('@/views/About.vue')
}
]
},
{
path: '/login',
name: 'Login',
component: () => import('@/views/Login.vue'),
meta: { public: true }
}
]
const router = createRouter({
history: createWebHistory(),
routes
})
// 路由守卫
router.beforeEach((to, from, next) => {
const userStore = useUserStore()
if (!to.meta.public && !userStore.isLoggedIn) {
next('/login')
} else {
next()
}
})
export default router
4.3 Pinia 状态管理
// src/stores/user.ts
import { defineStore } from 'pinia'
import { ref, computed } from 'vue'
export const useUserStore = defineStore('user', () => {
// State
const token = ref(localStorage.getItem('token') || '')
const userInfo = ref(null)
// Getters
const isLoggedIn = computed(() => !!token.value)
// Actions
const login = async (credentials: { username: string; password: string }) => {
// API 调用
const { data } = await api.post('/auth/login', credentials)
token.value = data.token
userInfo.value = data.user
localStorage.setItem('token', data.token)
}
const logout = () => {
token.value = ''
userInfo.value = null
localStorage.removeItem('token')
}
return {
token,
userInfo,
isLoggedIn,
login,
logout
}
})
4.4 组合式函数
// src/composables/useTable.ts
import { ref, computed } from 'vue'
export function useTable<T>(fetchFn: (params: any) => Promise<{ list: T[]; total: number }>) {
const loading = ref(false)
const data = ref<T[]>([])
const total = ref(0)
const currentPage = ref(1)
const pageSize = ref(10)
const pagination = computed(() => ({
currentPage: currentPage.value,
pageSize: pageSize.value,
total: total.value
}))
const fetchData = async (params = {}) => {
loading.value = true
try {
const result = await fetchFn({
page: currentPage.value,
size: pageSize.value,
...params
})
data.value = result.list
total.value = result.total
} finally {
loading.value = false
}
}
const handlePageChange = (page: number) => {
currentPage.value = page
fetchData()
}
return {
loading,
data,
pagination,
fetchData,
handlePageChange
}
}
第五步:实战示例
5.1 用户列表页面
<!-- src/views/UserList.vue -->
<template>
<div class="user-list">
<el-table :data="data" v-loading="loading">
<el-table-column prop="id" label="ID" width="80" />
<el-table-column prop="username" label="用户名" />
<el-table-column prop="email" label="邮箱" />
<el-table-column prop="createdAt" label="创建时间">
<template #default="{ row }">
{{ formatDate(row.createdAt) }}
</template>
</el-table-column>
<el-table-column label="操作" width="150">
<template #default="{ row }">
<el-button type="primary" size="small" @click="editUser(row)">
编辑
</el-button>
</template>
</el-table-column>
</el-table>
<el-pagination
v-model:current-page="pagination.currentPage"
v-model:page-size="pagination.pageSize"
:total="pagination.total"
@current-change="handlePageChange"
layout="total, prev, pager, next"
/>
</div>
</template>
<script setup lang="ts">
import { useTable } from '@/composables/useTable'
import { formatDate } from '@/utils/date'
import { getUserList } from '@/api/user'
const { loading, data, pagination, fetchData, handlePageChange } = useTable(getUserList)
// 初始加载
fetchData()
const editUser = (user: any) => {
// 编辑逻辑
}
</script>
5.2 封装 Axios
// src/utils/request.ts
import axios from 'axios'
import { useUserStore } from '@/stores/user'
import { ElMessage } from 'element-plus'
const request = axios.create({
baseURL: import.meta.env.VITE_API_BASE_URL,
timeout: 10000
})
// 请求拦截器
request.interceptors.request.use((config) => {
const userStore = useUserStore()
if (userStore.token) {
config.headers.Authorization = `Bearer ${userStore.token}`
}
return config
})
// 响应拦截器
request.interceptors.response.use(
(response) => response.data,
(error) => {
if (error.response?.status === 401) {
const userStore = useUserStore()
userStore.logout()
window.location.href = '/login'
} else {
ElMessage.error(error.response?.data?.message || '请求失败')
}
return Promise.reject(error)
}
)
export default request
第六步:构建与部署
# 开发
pnpm dev
# 构建(测试环境)
pnpm build
# 构建(生产环境)
pnpm build --mode production
# 预览生产构建
pnpm preview
Nginx 配置
server {
listen 80;
server_name myapp.example.com;
root /var/www/my-vue-app/dist;
index index.html;
location / {
try_files $uri $uri/ /index.html;
}
location /api {
proxy_pass http://backend_server;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
# 静态资源缓存
location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg)$ {
expires 1y;
add_header Cache-Control "public, immutable";
}
}
性能优化建议
- 路由懒加载:使用
() => import()按需加载 - 组件自动导入:使用
unplugin-vue-components - 代码分割:配置
manualChunks分离第三方库 - 图片优化:使用 WebP 格式,配置懒加载
- Gzip 压缩:Nginx 开启 gzip
总结
Vue 3 + Vite 的组合提供了极致的开发体验:
- ⚡ 快速的冷启动和热更新
- 🎯 原生 TypeScript 支持
- 📦 优化的构建输出
- 🔧 丰富的插件生态
讨论: 你在 Vue 3 项目中遇到过哪些坑?欢迎分享解决方案!

![[知识大区] Vue 3 + Vite 项目实战:从 0 搭建现代化前端工程](https://bbs.lgdfort.com/content/uploadfile/202606/e0411781405253.webp)




