Code前端首页关于Code前端联系我们

Vue3 + Element Admin 新手入门难?项目搭建到实战全流程答疑

terry 1小时前 阅读数 41 #Vue
文章标签 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-componentsunplugin-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-componentsunplugin-auto-import 插件!它们会自动分析代码中用到的Element Plus组件/APIElButton 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变量覆盖,先装 sassnpm 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简洁太多:

  • 不需要分 mutations actions,只有 state getters actions,写法更灵活;
  • 天然支持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前端网发表,如需转载,请注明页面地址。

热门