Vue3 + Element Admin 新手入门难?项目搭建到实战全流程答疑
从0到1搭框架
用什么工具搭建Vue3项目?
现在Vue3生态里,Vite 是官方推荐的新一代构建工具,热更新和打包速度比Vue CLI更快更轻量,优先选它,操作步骤很简单:
打开终端执行 npm create vite@latest my-admin -- --template vue 创建项目,进入目录 cd my-admin 后执行 npm install 安装依赖,就能得到最基础的Vue3 + Vite项目,如果团队习惯Vue CLI,也能通过 vue create my-admin 选择Vue3版本,但Vite的开发体验更优,建议优先尝试。
怎么集成Element Plus到Vue3项目?
先安装Element Plus:npm install element-plus,引入方式分全局引入和按需引入:
-
全局引入(适合小项目,简单但体积大):
在main.js里全局注册:import { createApp } from 'vue' import App from './App.vue' import ElementPlus from 'element-plus' import 'element-plus/dist/index.css' const app = createApp(App) app.use(ElementPlus) app.mount('#app') -
按需引入(适合中大型项目,减小打包体积):
需要配合unplugin-vue-components和unplugin-auto-import两个Vite插件,先装插件:npm install -D unplugin-vue-components unplugin-auto-import,再在vite.config.js配置: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 { ElementPlusResolver } from 'unplugin-vue-components/resolvers' export default defineConfig({ plugins: [ vue(), AutoImport({ resolvers: [ElementPlusResolver()] }), Components({ resolvers: [ElementPlusResolver()] }), ], })这样会自动导入用到的Element Plus组件和API,不用手动
import,还能只打包用到的部分。
基础项目结构该怎么规划?
后台管理系统需按业务模块分层管理,推荐结构:
src/
├─ api/ # 接口请求层(按业务分文件,如user.js、goods.js)
├─ assets/ # 静态资源(图片、样式、图标)
├─ components/ # 通用组件(布局、按钮、表格封装)
├─ router/ # 路由配置(主路由、权限路由逻辑)
├─ store/ # 状态管理(Pinia/Vuex,按模块拆分)
├─ utils/ # 工具函数(请求封装、权限判断)
├─ views/ # 页面级组件(用户管理、订单管理等页面)
├─ App.vue # 根组件
├─ main.js # 入口文件
每个目录职责明确,api 按业务聚合接口,store 用Pinia时按模块拆分成 userStore.js appStore.js 等,后期维护更清晰。
路由与权限:后台系统的核心骨架
Vue Router在后台管理里怎么配置多层路由?
后台常有侧边栏菜单,对应嵌套路由(如一级菜单 /dashboard,二级 /dashboard/analysis),在 router/index.js 里这样配:
import { createRouter, createWebHistory } from 'vue-router'
import Layout from '@/components/Layout/index.vue' // 通用布局组件
const routes = [
{
path: '/',
component: Layout,
redirect: '/dashboard',
children: [
{
path: 'dashboard',
name: 'Dashboard',
component: () => import('@/views/Dashboard/index.vue'),
meta: { title: '仪表盘', icon: 'HomeFilled' } // 用于侧边栏渲染
},
{
path: 'user',
name: 'User',
component: () => import('@/views/User/index.vue'),
meta: { title: '用户管理', icon: 'UserFilled' }
}
]
},
{
path: '/login',
name: 'Login',
component: () => import('@/views/Login/index.vue')
}
]
const router = createRouter({
history: createWebHistory(import.meta.env.BASE_URL),
routes
})
export default router
Layout 组件负责渲染侧边栏、顶栏和内容区域,children 里的路由对应内容区页面。
动态路由和权限拦截怎么实现?
权限分菜单权限和按钮权限,动态路由常用于按角色加载菜单,核心步骤:
-
登录后加载路由:登录成功后,从后端获取用户角色和可访问路由列表,用
router.addRoute动态添加。 -
路由守卫拦截:在
router.beforeEach里判断权限,示例(结合Pinia):import router from '@/router' import { useUserStore } from '@/store/user' router.beforeEach(async (to, from, next) => { const userStore = useUserStore() if (!userStore.token && to.name !== 'Login') { // 未登录且非登录页 → 跳登录 next({ name: 'Login' }) } else if (userStore.token && to.name === 'Login') { // 已登录跳登录页 → 重定向首页 next({ name: 'Dashboard' }) } else { // 检查路由权限(假设meta.roles定义允许角色) if (userStore.roles.some(role => to.meta.roles?.includes(role))) { next() } else { next({ name: '403' }) // 无权限页 } } })
面包屑导航怎么自动生成?
利用 router.currentRoute.value.matched 数组(包含当前路由的所有层级),在布局组件里遍历生成,示例:
<template>
<el-breadcrumb separator="/">
<el-breadcrumb-item
v-for="(item, index) in breadcrumbList"
:key="index"
>
{{ item.meta.title }}
</el-breadcrumb-item>
</el-breadcrumb>
</template>
<script setup>
import { computed } from 'vue'
import { useRoute } from 'vue-router'
const route = useRoute()
const breadcrumbList = computed(() => {
return route.matched.filter(item => item.meta && item.meta.title)
})
</script>
每个路由的 meta 里配置 title,就能自动生成面包屑。
组件与UI:Element Plus高效开发技巧
Element Plus组件怎么按需引入减少体积?
靠前面提的 unplugin-vue-components 和 unplugin-auto-import 插件!它们会自动分析代码中用到的Element Plus组件/API(ElButton ElMessage),只打包这些部分,而非整个库,Vue CLI项目则用 babel-plugin-component 实现类似逻辑,核心是“用多少打包多少”。
表格(Table)和表单(Form)怎么结合Vue3语法封装?
表格是后台高频组件,建议封装成通用BaseTable,支持动态列、分页、插槽扩展,示例:
<template>
<el-table :data="tableData" :columns="columns" border @selection-change="handleSelect">
<template #header-cell="{ column }">{{ column.label }}</template>
<template #default="{ row, column }">
<slot :row="row" :column="column" name="custom" />
</template>
</el-table>
<el-pagination
layout="total, sizes, prev, pager, next, jumper"
:total="total"
:page-size="pageSize"
@size-change="handleSizeChange"
@page-change="handlePageChange"
/>
</template>
<script setup>
import { defineProps, defineEmits } from 'vue'
const props = defineProps({
tableData: { type: Array, required: true },
columns: { type: Array, required: true },
total: { type: Number, default: 0 },
pageSize: { type: Number, default: 10 }
})
const emit = defineEmits(['selection-change', 'size-change', 'page-change'])
const handleSelect = (selection) => emit('selection-change', selection)
const handleSizeChange = (size) => emit('size-change', size)
const handlePageChange = (page) => emit('page-change', page)
</script>
使用时通过 columns 配置列,插槽自定义操作按钮:
<BaseTable
:table-data="userList"
:columns="columns"
:total="total"
@page-change="getUserList"
>
<template #custom="{ row }">
<el-button type="text" @click="editUser(row)">编辑</el-button>
</template>
</BaseTable>
<script setup>
const columns = [
{ prop: 'name', label: '用户名' },
{ prop: 'email', label: '邮箱' },
{ label: '操作', width: 180 }
]
</script>
自定义主题怎么改Element Plus的样式?
用SCSS变量覆盖,先装 sass:npm install sass,再在 styles/element/index.scss 里修改变量:
@forward 'element-plus/theme-chalk/src/common/var.scss' with (
$colors: (
'primary': (
'base': #409eff // 替换主色
)
)
);
@use 'element-plus/dist/index.css' as *; // 导入Element Plus样式
最后在 main.js 里引入这个SCSS文件,替代原来的 element-plus/dist/index.css:
import './styles/element/index.scss'
这样就能修改主色、圆角、字体等,实现主题定制。
状态管理:Pinia vs Vuex怎么选?
为什么说Pinia更适合Vue3项目?
Pinia是Vue官方团队成员开发的,专为Vue3设计,API比Vuex简洁太多:
- 不需要分
mutationsactions,只有stategettersactions,写法更灵活; - 天然支持Composition API,代码组织更自由;
- 体积更小、性能更好,还支持服务端渲染。
现在Vue3项目优先选Pinia,它是Vuex的“接班人”。
Pinia怎么拆分模块管理不同业务状态?
通过定义多个Store拆分模块,比如用户模块 userStore.js:
import { defineStore } from 'pinia'
export const useUserStore = defineStore('user', {
state: () => ({
token: '',
roles: [],
userInfo: {}
}),
getters: {
isAdmin: (state) => state.roles.includes('admin')
},
actions: {
setToken(token) {
this.token = token
},
async login(userInfo) {
const res = await loginApi(userInfo) // 调用登录接口
this.setToken(res.token)
}
}
})
应用模块 appStore.js 管理侧边栏、主题等:
import { defineStore } from 'pinia'
export const useAppStore = defineStore('app', {
state: () => ({
sidebarCollapse: false,
theme: 'light'
}),
actions: {
toggleSidebar() {
this.sidebarCollapse = !this.sidebarCollapse
}
}
})
组件中按需引入使用:
<script setup>
import { useUserStore } from '@/store/user'
import { useAppStore } from '@/store/app'
const userStore = useUserStore()
const appStore = useAppStore()
userStore.login({ username: 'admin', password: '123' })
appStore.toggleSidebar()
</script>
全局加载状态(比如接口请求loading)怎么用Pinia控制?
创建 loadingStore.js 管理加载状态:
import { defineStore } from 'pinia'
export const useLoadingStore = defineStore('loading', {
state: () => ({
globalLoading: false,
requestCount: 0 // 计数防止loading闪烁
}),
actions: {
showLoading() {
this.requestCount++
this.globalLoading = true
},
hideLoading() {
this.requestCount--
if (this.requestCount <= 0) {
this.globalLoading = false
this.requestCount = 0
}
}
}
})
在axios拦截器里调用:
import { useLoadingStore } from '@/store/loading'
const service = axios.create({ /* 配置 */ })
service.interceptors.request.use(
(config) => {
const loadingStore = useLoadingStore()
loadingStore.showLoading()
return config
}
)
service.interceptors.response.use(
(response) => {
const loadingStore = useLoadingStore()
loadingStore.hideLoading()
return response
}
)
这样所有接口请求时显示全局loading,完成后隐藏,多个请求也能正确控制。
接口与工具:前后端交互层设计
axios怎么封装成请求工具?
创建 utils/request.js 封装axios,统一处理请求拦截、响应拦截、错误提示:
import axios from 'axios'
import { ElMessage } from 'element-plus'
import { useUserStore } from '@/store/user'
const service = axios.create({
baseURL: import.meta.env.VITE_API_BASE_URL, // 从环境变量取基础地址
timeout: 5000
})
// 请求拦截:加token
service.interceptors.request.use(
(config) => {
const userStore = useUserStore()
if (userStore.token) {
config.headers.Authorization = `Bearer ${userStore.token}`
}
return config
}
)
// 响应拦截:统一错误处理
service.interceptors.response.use(
(response) => {
const res = response.data
if (res.code !== 200) { // 假设后端code=200为成功
ElMessage.error(res.msg || '请求失败')
if (res.code === 401) { // token失效 → 清token跳登录
userStore.setToken('')
window.location.href = '/login'
}
return Promise.reject(new Error(res.msg))
}
return res
},
(error) => {
ElMessage.error(error.message || '网络错误')
return Promise.reject(error)
}
)
export default service
然后在 api 目录按业务建文件(如 user.js)管理接口:
import request from '@/utils/request'
// 登录
export function login(data) {
return request({ url: '/auth/login', method: 'post', data })
}
// 获取用户列表
export function getUserList(params) {
return request({ url: '/user/list', method: 'get', params })
}
mock数据在开发阶段怎么用?
用 vite-plugin-mock 模拟后端接口,安装:npm install -D vite-plugin-mock,在 vite.config.js 配置:
import { viteMockServe } from 'vite-plugin-mock'
export default defineConfig({
plugins: [
vue(),
viteMockServe({
mockPath: 'mock', // mock数据目录
localEnabled: true // 开发环境启用
})
]
})
在 mock 目录建 user.js 写模拟逻辑:
export default [
{
url: '/auth/login',
method: 'post',
response: ({ body }) => {
if (body.username === 'admin' && body.password === '123') {
return { code: 200, data: { token: 'mock_token' } }
}
return { code: 400, msg: '账号错误' }
}
}
]
开发时无需后端接口,联调时关闭mock(localEnabled: false)即可。
实战场景:典型页面开发案例
用户管理页面(表格+分页+弹窗)怎么写?
结合Element Plus的Table、Pagination、Dialog、Form,实现增删改查:
<template> <div
版权声明
本文仅代表作者观点,不代表Code前端网立场。
本文系作者Code前端网发表,如需转载,请注明页面地址。
code前端网


