Vue3\+Vite\+Pinia实战:企业级后台管理系统完整实现(附源码)

摘要:Vue3作为前端主流框架,结合Vite的快速构建能力与Pinia的状态管理优势,已成为企业级后台管理系统的首选技术栈。本文基于Vue3+Vite+Pinia+Element Plus,实现一个完整的企业级后台管理系统,包含用户登录、权限控制、数据表格、表单提交、图表展示等核心功能,附完整源码与部署教程,兼顾美观与实用性,适合前端开发者快速上手Vue3项目。

一、前言:Vue3技术栈的优势与项目选型

传统Vue2项目面临构建速度慢、状态管理繁琐(Vuex)、TypeScript支持不佳等问题,而Vue3+Vite+Pinia技术栈完美解决了这些痛点:Vite基于ESModule实现快速热更新,构建速度比Webpack快5-10倍;Pinia替代Vuex,API更简洁、支持TypeScript、无需 mutations,状态管理更高效;Element Plus作为Vue3生态的UI组件库,组件丰富、适配Vue3,可快速搭建美观的后台界面。

本文将实现一个企业级后台管理系统(用户管理、角色管理、菜单管理、数据统计),覆盖后台系统的核心场景,同时讲解Vue3的Composition API、自定义Hook、路由守卫、权限控制等核心知识点,帮助开发者快速掌握Vue3实战技巧。

二、环境搭建:Vue3+Vite+Pinia项目初始化

本次实战基于Node.js 18、Vue3.4、Vite5.0、Pinia 2.1、Element Plus 2.7,建议使用npm或pnpm初始化项目,避免版本冲突。

bash 复制代码
# 1. 初始化Vite+Vue3项目
npm create vite@latest admin-system -- --template vue-ts

# 2. 进入项目目录
cd admin-system

# 3. 安装核心依赖
npm install pinia element-plus @element-plus/icons-vue axios vue-router

# 4. 启动开发服务器
npm run dev

项目目录结构设计(规范清晰,便于维护):

text 复制代码
admin-system/
├── src/
│   ├── api/          # 接口请求封装
│   ├── assets/       # 静态资源(图片、样式)
│   ├── components/   # 公共组件
│   ├── hooks/        # 自定义Hook
│   ├── router/       # 路由配置
│   ├── store/        # Pinia状态管理
│   ├── utils/        # 工具函数
│   ├── views/        # 页面组件
│   ├── App.vue       # 根组件
│   ├── main.ts       # 入口文件
│   └── vite-env.d.ts # 类型声明
├── vite.config.ts    # Vite配置
└── package.json      # 依赖配置

三、核心功能实现(分模块讲解+源码)

3.1 路由配置与权限控制

核心逻辑:使用vue-router实现路由配置,通过路由守卫判断用户是否登录、是否拥有对应权限,未登录用户跳转至登录页,无权限用户跳转至403页面。

typescript 复制代码
// src/router/index.ts
import { createRouter, createWebHistory, RouteRecordRaw } from 'vue-router'
import { useUserStore } from '@/store/userStore'

// 公开路由(无需登录)
const publicRoutes: RouteRecordRaw[] = [
  {
    path: '/login',
    name: 'Login',
    component: () => import('@/views/Login.vue')
  },
  {
    path: '/403',
    name: 'Forbidden',
    component: () => import('@/views/403.vue')
  }
]

// 私有路由(需要登录+权限)
const privateRoutes: RouteRecordRaw[] = [
  {
    path: '/',
    name: 'Layout',
    component: () => import('@/components/Layout.vue'),
    redirect: '/dashboard',
    children: [
      {
        path: 'dashboard',
        name: 'Dashboard',
        component: () => import('@/views/Dashboard.vue'),
        meta: { title: '数据面板', permission: ['admin', 'user'] }
      },
      {
        path: 'user',
        name: 'User',
        component: () => import('@/views/User.vue'),
        meta: { title: '用户管理', permission: ['admin'] }
      },
      {
        path: 'role',
        name: 'Role',
        component: () => import('@/views/Role.vue'),
        meta: { title: '角色管理', permission: ['admin'] }
      }
    ]
  }
]

const router = createRouter({
  history: createWebHistory(),
  routes: [...publicRoutes, ...privateRoutes]
})

// 路由守卫(权限控制)
router.beforeEach((to, from, next) => {
  const userStore = useUserStore()
  const token = userStore.token

  // 未登录用户,只能访问公开路由
  if (!token && to.name !== 'Login') {
    next({ name: 'Login' })
    return
  }

  // 已登录用户,禁止访问登录页
  if (token && to.name === 'Login') {
    next({ name: 'Dashboard' })
    return
  }

  // 权限判断(私有路由需要对应权限)
  if (to.meta.permission && token) {
    const permission = to.meta.permission as string[]
    if (!permission.includes(userStore.role)) {
      next({ name: 'Forbidden' })
      return
    }
  }

  next()
})

export default router

3.2 Pinia状态管理(用户+权限)

核心逻辑:使用Pinia管理用户状态(token、用户名、角色),实现登录、登出、状态持久化(localStorage),避免页面刷新后状态丢失。

typescript 复制代码
// src/store/userStore.ts
import { defineStore } from 'pinia'
import { loginApi, logoutApi } from '@/api/userApi'

interface UserState {
  token: string | null
  username: string | null
  role: string | null // admin/user
}

export const useUserStore = defineStore('user', {
  state: (): UserState => ({
    token: localStorage.getItem('token'),
    username: localStorage.getItem('username'),
    role: localStorage.getItem('role')
  }),
  actions: {
    // 登录
    async login(data: { username: string; password: string }) {
      const res = await loginApi(data)
      const { token, username, role } = res.data
      // 存储状态
      this.token = token
      this.username = username
      this.role = role
      // 持久化到localStorage
      localStorage.setItem('token', token)
      localStorage.setItem('username', username)
      localStorage.setItem('role', role)
      return res
    },
    // 登出
    async logout() {
      await logoutApi()
      // 清空状态
      this.token = null
      this.username = null
      this.role = null
      // 清空localStorage
      localStorage.removeItem('token')
      localStorage.removeItem('username')
      localStorage.removeItem('role')
    }
  }
})

3.3 接口请求封装(Axios)

核心逻辑:封装Axios,统一处理请求拦截(添加token)、响应拦截(处理错误、token过期),简化接口调用,提升代码复用性。

typescript 复制代码
// src/utils/request.ts
import axios from 'axios'
import { useUserStore } from '@/store/userStore'
import router from '@/router'

// 创建Axios实例
const request = axios.create({
  baseURL: import.meta.env.VITE_API_BASE_URL, // 环境变量配置
  timeout: 5000
})

// 请求拦截器(添加token)
request.interceptors.request.use(
  (config) => {
    const userStore = useUserStore()
    if (userStore.token) {
      config.headers.Authorization = `Bearer ${userStore.token}`
    }
    return config
  },
  (error) => {
    return Promise.reject(error)
  }
)

// 响应拦截器(处理错误)
request.interceptors.response.use(
  (response) => {
    return response.data
  },
  (error) => {
    // token过期,清空状态并跳转登录页
    if (error.response?.status === 401) {
      const userStore = useUserStore()
      userStore.logout()
      router.push({ name: 'Login' })
    }
    return Promise.reject(error)
  }
)

export default request

3.4 核心页面实现(用户管理+数据面板)

用户管理页面:使用Element Plus的Table组件展示用户列表,实现分页、搜索、新增、编辑、删除功能;数据面板页面:使用ECharts展示数据统计图表(折线图、饼图),直观展示系统数据。

vue 复制代码
<!-- src/views/User.vue(用户管理页面) -->
<template>
  <el-page-header content="用户管理" />
  <el-card class="mt-4">
    <div class="flex justify-between mb-4">
      <el-input v-model="searchKeyword" placeholder="请输入用户名搜索" style="width: 300px" />
      <el-button type="primary" @click="openAddDialog">新增用户</el-button>
    </div>
    <el-table :data="userList" border stripe>
      <el-table-column label="ID" prop="id" align="center" />
      <el-table-column label="用户名" prop="username" align="center" />
      <el-table-column label="角色" prop="role" align="center">
        <template #default="scope">
          <el-tag :type="scope.row.role === 'admin' ? 'primary' : 'success'">
            {{ scope.row.role === 'admin' ? '管理员' : '普通用户' }}
          </el-tag>
        </template>
      </el-table-column>
      <el-table-column label="操作" align="center">
        <template #default="scope">
          <el-button type="text" @click="openEditDialog(scope.row)">编辑</el-button>
          <el-button type="text" danger @click="deleteUser(scope.row.id)">删除</el-button>
        </template>
      </el-table-column>
    </el-table>
    <el-pagination
      class="mt-4"
      :total="total"
      :page-size="pageSize"
      :current-page="currentPage"
      @current-change="handlePageChange"
      layout="total, prev, pager, next"
    />
  </el-card>

<!-- 新增/编辑用户弹窗 -->
  <el-dialog v-model="dialogVisible" :title="dialogTitle" width="500px">
    <el-form :model="form" :rules="rules" ref="formRef" label-width="100px">
      <el-form-item label="用户名" prop="username">
        <el-input v-model="form.username" />
      </el-form-item>
      <el-form-item label="密码" prop="password" v-if="isAdd">
        <el-input v-model="form.password" type="password" />
      </el-form-item>
      <el-form-item label="角色" prop="role">
        <el-select v-model="form.role" placeholder="请选择角色">
          <el-option label="管理员" value="admin" />
          <el-option label="普通用户" value="user" />
        </el-select>
      </el-form-item>
    </el-form>
    <template #footer>
      <el-button @click="dialogVisible = false">取消</el-button>
      <el-button type="primary" @click="submitForm">确定</el-button>
    </template>
  </el-dialog>
</template>

<script setup lang="ts">
import { ref, reactive, onMounted } from 'vue'
import { ElMessage, ElMessageBox } from 'element-plus'
import { getUserListApi, addUserApi, editUserApi, deleteUserApi } from '@/api/userApi'

// 搜索与分页
const searchKeyword = ref('')
const total = ref(0)
const pageSize = ref(10)
const currentPage = ref(1)
const userList = ref([])

// 弹窗相关
const dialogVisible = ref(false)
const isAdd = ref(true)
const dialogTitle = ref('新增用户')
const formRef = ref()
const form = reactive({
  username: '',
  password: '',
  role: 'user'
})
const rules = reactive({
  username: [{ required: true, message: '请输入用户名', trigger: 'blur' }],
  password: [{ required: true, message: '请输入密码', trigger: 'blur' }],
  role: [{ required: true, message: '请选择角色', trigger: 'change' }]
})

// 获取用户列表
const getUserList = async () => {
  const res = await getUserListApi({
    keyword: searchKeyword.value,
    page: currentPage.value,
    size: pageSize.value
  })
  userList.value = res.data.list
  total.value = res.data.total
}

// 分页切换
const handlePageChange = (page: number) => {
  currentPage.value = page
  getUserList()
}

// 新增用户弹窗
const openAddDialog = () => {
  isAdd.value = true
  dialogTitle.value = '新增用户'
  form.username = ''
  form.password = ''
  form.role = 'user'
  dialogVisible.value = true
}

// 编辑用户弹窗
const openEditDialog = (row: any) => {
  isAdd.value = false
  dialogTitle.value = '编辑用户'
  form.username = row.username
  form.role = row.role
  dialogVisible.value = true
}

// 提交表单(新增/编辑)
const submitForm = async () => {
  await formRef.value.validate()
  if (isAdd.value) {
    await addUserApi(form)
    ElMessage.success('新增用户成功')
  } else {
    await editUserApi(form)
    ElMessage.success('编辑用户成功')
  }
  dialogVisible.value = false
  getUserList()
}

// 删除用户
const deleteUser = async (id: number) => {
  await ElMessageBox.confirm('确定要删除该用户吗?', '提示', {
    type: 'warning'
  })
  await deleteUserApi(id)
  ElMessage.success('删除用户成功')
  getUserList()
}

// 页面加载时获取用户列表
onMounted(() => {
  getUserList()
})
</script>

四、项目优化与部署

4.1 项目优化策略

  1. 按需引入:Element Plus使用按需引入,减少打包体积;

  2. 路由懒加载:使用动态import()实现路由懒加载,提升首屏加载速度;

  3. 自定义Hook:封装常用Hook(如useRequest、usePermission),提升代码复用性;

  4. 样式优化:使用Scoped CSS避免样式污染,结合Tailwind CSS简化样式编写;

  5. 性能优化:使用v-memo缓存组件,避免不必要的重渲染;图片使用懒加载。

4.2 项目部署(Nginx)

bash 复制代码
# 1. 打包项目
npm run build

# 2. 部署到Nginx(修改nginx.conf配置)
server {
  listen 80;
  server_name localhost;

  location / {
    root /usr/local/nginx/html/admin-system; # 打包后的dist目录路径
    index index.html;
    try_files $uri $uri/ /index.html; # 解决Vue路由刷新404问题
  }

  # 接口代理(解决跨域)
  location /api {
    proxy_pass http://localhost:8080; # 后端接口地址
    proxy_set_header Host $host;
    proxy_set_header X-Real-IP $remote_addr;
  }
}

五、总结与延伸

本文完整实现了企业级后台管理系统的核心功能,掌握了Vue3+Vite+Pinia+Element Plus的实战用法,同时学习了路由守卫、权限控制、接口封装、项目优化与部署等核心知识点。该项目可直接作为企业级后台系统的模板,根据业务需求扩展功能。

延伸学习:可深入研究Vue3的自定义组件开发、Vue3+TS的类型约束、ECharts复杂图表的实现;同时学习前端工程化(ESLint、Prettier、Husky),规范代码风格,提升开发效率。

相关推荐
Cdlblbq6 小时前
搜索会员中心 创作中心Vue2项目一键打包成桌面应用
前端·javascript·vue.js·electron
A_nanda8 小时前
vue实现后端传输逐帧图像数据
前端·javascript·vue.js
当时只道寻常8 小时前
Vue3 集成 NProgress 进度条:从入门到精通
前端·vue.js
米丘8 小时前
Vue 3.x 单文件组件(SFC)模板编译过程解析
前端·vue.js·编译原理
米丘9 小时前
vue3.x 内置指令有哪些?
前端·vue.js
米丘9 小时前
Vue 3.x 模板编译优化:静态提升、预字符串化与 Block Tree
前端·vue.js·编译原理
空中海10 小时前
第一章:Vue 基础与模板语法
前端·javascript·vue.js
许彰午11 小时前
Spring Boot + Vue 实现 XML 动态表单:固定字段 + 自由扩展方案
xml·vue.js·spring boot
前端那点事11 小时前
Vue并发控制|几十个请求高效管控(实战方案+可运行代码)
前端·vue.js