尧图精选

Vue3+Node.js全栈后台管理系统实战:从零搭建企业级平台

🕒 发布时间:2026/9/3 14:46:50 📁 来源:尧图网络
Vue3Node.js全栈后台管理系统实战从零搭建企业级管理平台在当今快速发展的互联网时代前后端分离的开发模式已成为企业级应用开发的主流。Vue3作为前端框架的新星以其优秀的性能和新特性备受开发者青睐而Node.js凭借其高效的I/O处理能力成为后端开发的热门选择。本文将带领大家从零开始完整搭建一个基于Vue3全家桶Node.jsMySQL的全栈后台管理系统涵盖用户管理、权限控制、数据可视化等核心功能。无论你是刚接触Vue3的初学者还是希望提升全栈开发能力的进阶开发者本文都将为你提供一套完整的实战方案。我们将从环境搭建开始逐步深入核心功能实现最后部署上线让你真正掌握企业级项目的开发流程。1. 项目概述与技术选型1.1 项目需求分析后台管理系统是现代企业信息化建设的重要组成部分通常需要具备以下核心功能用户认证与权限管理实现用户登录、注册、权限验证等功能数据管理对业务数据进行增删改查操作数据可视化通过图表展示关键业务指标系统配置管理系统的各项参数和设置日志记录记录用户操作和系统运行状态1.2 技术栈选择理由前端技术栈Vue3全家桶Vue3采用Composition API更好的TypeScript支持性能优化明显Vue Router官方路由管理器支持路由守卫和懒加载Pinia新一代状态管理库替代Vuex更简洁的API设计Element Plus基于Vue3的UI组件库丰富的组件和良好的用户体验后端技术栈Node.js ExpressNode.js非阻塞I/O模型适合I/O密集型应用Express轻量灵活的Web框架中间件生态丰富JWT无状态的身份验证机制bcrypt密码加密存储提升安全性数据库MySQL关系型数据库数据一致性有保障丰富的生态系统和工具支持适合管理结构化数据2. 开发环境搭建2.1 前端开发环境配置首先确保你的系统已安装Node.js版本建议16.x以上和npm包管理器# 检查Node.js和npm版本 node --version npm --version创建Vue3项目# 使用Vite创建项目推荐 npm create vuelatest vue3-admin-frontend cd vue3-admin-frontend # 安装依赖 npm install # 安装必要依赖包 npm install vue-router4 pinia element-plus element-plus/icons-vue npm install axios qs dayjs echarts -D项目目录结构规划src/ ├── api/ # 接口封装 ├── assets/ # 静态资源 ├── components/ # 公共组件 ├── router/ # 路由配置 ├── store/ # 状态管理 ├── utils/ # 工具函数 ├── views/ # 页面组件 ├── App.vue # 根组件 └── main.js # 入口文件2.2 后端开发环境配置创建Node.js项目# 初始化项目 mkdir vue3-admin-backend cd vue3-admin-backend npm init -y # 安装核心依赖 npm install express cors helmet morgan npm install mysql2 bcryptjs jsonwebtoken npm install dotenv joi express-validator npm install nodemon -D后端项目结构src/ ├── config/ # 配置文件 ├── controllers/ # 控制器 ├── middleware/ # 中间件 ├── models/ # 数据模型 ├── routes/ # 路由定义 ├── utils/ # 工具函数 ├── app.js # 应用入口 └── server.js # 服务器启动文件2.3 数据库环境配置安装MySQL数据库以Windows为例下载MySQL Installer并安装配置root用户密码创建项目数据库CREATE DATABASE vue3_admin DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; USE vue3_admin; -- 用户表 CREATE TABLE users ( id INT PRIMARY KEY AUTO_INCREMENT, username VARCHAR(50) UNIQUE NOT NULL, password VARCHAR(255) NOT NULL, email VARCHAR(100), role ENUM(admin, user) DEFAULT user, status TINYINT DEFAULT 1, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP ); -- 菜单权限表 CREATE TABLE menus ( id INT PRIMARY KEY AUTO_INCREMENT, parent_id INT DEFAULT 0, title VARCHAR(50) NOT NULL, path VARCHAR(100), icon VARCHAR(50), sort INT DEFAULT 0, status TINYINT DEFAULT 1 ); -- 操作日志表 CREATE TABLE operation_logs ( id INT PRIMARY KEY AUTO_INCREMENT, user_id INT NOT NULL, action VARCHAR(100) NOT NULL, method VARCHAR(10), path VARCHAR(200), ip VARCHAR(45), user_agent TEXT, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP );3. 前端核心功能实现3.1 Vue3项目初始化配置在main.js中完成基础配置import { createApp } from vue import { createPinia } from pinia import ElementPlus from element-plus import element-plus/dist/index.css import * as ElementPlusIconsVue from element-plus/icons-vue import App from ./App.vue import router from ./router const app createApp(App) // 注册Element Plus图标 for (const [key, component] of Object.entries(ElementPlusIconsVue)) { app.component(key, component) } app.use(createPinia()) app.use(router) app.use(ElementPlus) app.mount(#app)路由配置router/index.jsimport { createRouter, createWebHistory } from vue-router import { useUserStore } from /stores/user const routes [ { path: /login, name: Login, component: () import(/views/Login.vue), meta: { requiresAuth: false } }, { path: /, name: Layout, component: () import(/layouts/MainLayout.vue), meta: { requiresAuth: true }, redirect: /dashboard, children: [ { path: dashboard, name: Dashboard, component: () import(/views/Dashboard.vue), meta: { title: 仪表板 } }, { path: user-management, name: UserManagement, component: () import(/views/UserManagement.vue), meta: { title: 用户管理 } } ] } ] const router createRouter({ history: createWebHistory(), routes }) // 路由守卫 router.beforeEach((to, from, next) { const userStore useUserStore() if (to.meta.requiresAuth !userStore.token) { next(/login) } else { next() } }) export default router3.2 状态管理设计使用Pinia进行状态管理stores/user.jsimport { defineStore } from pinia import { ref } from vue import { loginAPI, getUserInfoAPI } from /api/user export const useUserStore defineStore(user, () { const token ref(localStorage.getItem(token) || ) const userInfo ref({}) const menus ref([]) // 用户登录 const login async (loginData) { try { const response await loginAPI(loginData) token.value response.data.token localStorage.setItem(token, token.value) await getUserInfo() return Promise.resolve(response) } catch (error) { return Promise.reject(error) } } // 获取用户信息 const getUserInfo async () { try { const response await getUserInfoAPI() userInfo.value response.data.userInfo menus.value response.data.menus return Promise.resolve(response) } catch (error) { return Promise.reject(error) } } // 用户退出 const logout () { token.value userInfo.value {} menus.value [] localStorage.removeItem(token) } return { token, userInfo, menus, login, getUserInfo, logout } })3.3 登录页面实现Login.vue组件实现template div classlogin-container el-card classlogin-card template #header div classlogin-header h2后台管理系统/h2 /div /template el-form :modelloginForm :rulesloginRules refloginFormRef keyup.enterhandleLogin el-form-item propusername el-input v-modelloginForm.username placeholder请输入用户名 prefix-iconUser sizelarge / /el-form-item el-form-item proppassword el-input v-modelloginForm.password typepassword placeholder请输入密码 prefix-iconLock sizelarge show-password / /el-form-item el-form-item el-button typeprimary sizelarge :loadingloading clickhandleLogin stylewidth: 100% {{ loading ? 登录中... : 登录 }} /el-button /el-form-item /el-form /el-card /div /template script setup import { ref, reactive } from vue import { useRouter } from vue-router import { ElMessage } from element-plus import { useUserStore } from /stores/user const router useRouter() const userStore useUserStore() const loginFormRef ref() const loading ref(false) const loginForm reactive({ username: , password: }) const loginRules { username: [ { required: true, message: 请输入用户名, trigger: blur } ], password: [ { required: true, message: 请输入密码, trigger: blur }, { min: 6, message: 密码长度不能少于6位, trigger: blur } ] } const handleLogin async () { if (!loginFormRef.value) return try { await loginFormRef.value.validate() loading.value true await userStore.login(loginForm) ElMessage.success(登录成功) router.push(/dashboard) } catch (error) { ElMessage.error(error.response?.data?.message || 登录失败) } finally { loading.value false } } /script style scoped .login-container { display: flex; justify-content: center; align-items: center; min-height: 100vh; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); } .login-card { width: 400px; border-radius: 8px; } .login-header { text-align: center; color: #333; } /style3.4 主布局设计MainLayout.vue布局组件template el-container classlayout-container !-- 侧边栏 -- el-aside :widthisCollapse ? 64px : 200px div classlogo span v-if!isCollapse管理系统/span /div el-menu :default-activeactiveMenu :collapseisCollapse router unique-opened background-color#304156 text-color#bfcbd9 active-text-color#409EFF sidebar-item v-formenu in menus :keymenu.id :itemmenu / /el-menu /el-aside el-container !-- 头部 -- el-header classheader div classheader-left el-icon clicktoggleCollapse Fold v-if!isCollapse / Expand v-else / /el-icon breadcrumb / /div div classheader-right el-dropdown commandhandleCommand span classuser-info el-avatar :size30 :srcuserStore.userInfo.avatar / span{{ userStore.userInfo.username }}/span /span template #dropdown el-dropdown-menu el-dropdown-item commandprofile个人中心/el-dropdown-item el-dropdown-item commandlogout退出登录/el-dropdown-item /el-dropdown-menu /template /el-dropdown /div /el-header !-- 主要内容区域 -- el-main classmain-content router-view / /el-main /el-container /el-container /template script setup import { ref, computed } from vue import { useRoute, useRouter } from vue-router import { useUserStore } from /stores/user import SidebarItem from /components/SidebarItem.vue import Breadcrumb from /components/Breadcrumb.vue const route useRoute() const router useRouter() const userStore useUserStore() const isCollapse ref(false) const activeMenu computed(() route.path) const menus computed(() userStore.menus) const toggleCollapse () { isCollapse.value !isCollapse.value } const handleCommand (command) { if (command logout) { userStore.logout() router.push(/login) } else if (command profile) { router.push(/profile) } } /script style scoped .layout-container { height: 100vh; } .logo { height: 60px; display: flex; align-items: center; justify-content: center; color: white; font-size: 18px; font-weight: bold; background-color: #2b2f3a; } .header { display: flex; align-items: center; justify-content: space-between; background-color: white; border-bottom: 1px solid #e6e6e6; } .header-left { display: flex; align-items: center; } .header-right .user-info { display: flex; align-items: center; cursor: pointer; } .main-content { background-color: #f0f2f5; padding: 20px; } /style4. 后端API开发4.1 Express应用配置app.js基础配置const express require(express) const cors require(cors) const helmet require(helmet) const morgan require(morgan) const { createConnection } require(./config/database) const app express() // 安全中间件 app.use(helmet()) // CORS配置 app.use(cors({ origin: process.env.FRONTEND_URL || http://localhost:3000, credentials: true })) // 日志记录 app.use(morgan(combined)) // 解析请求体 app.use(express.json({ limit: 10mb })) app.use(express.urlencoded({ extended: true })) // 数据库连接 createConnection() // 路由配置 app.use(/api/auth, require(./routes/auth)) app.use(/api/users, require(./routes/users)) app.use(/api/menus, require(./routes/menus)) // 错误处理中间件 app.use((err, req, res, next) { console.error(err.stack) res.status(500).json({ success: false, message: 服务器内部错误, error: process.env.NODE_ENV development ? err.message : {} }) }) // 404处理 app.use(*, (req, res) { res.status(404).json({ success: false, message: 接口不存在 }) }) module.exports app4.2 数据库连接配置config/database.jsconst mysql require(mysql2/promise) require(dotenv).config() const dbConfig { host: process.env.DB_HOST || localhost, port: process.env.DB_PORT || 3306, user: process.env.DB_USER || root, password: process.env.DB_PASSWORD || , database: process.env.DB_NAME || vue3_admin, charset: utf8mb4, connectionLimit: 10, acquireTimeout: 60000, timeout: 60000 } const pool mysql.createPool(dbConfig) const createConnection async () { try { const connection await pool.getConnection() console.log(数据库连接成功) connection.release() } catch (error) { console.error(数据库连接失败:, error) process.exit(1) } } // 执行SQL查询 const query async (sql, params) { try { const [results] await pool.execute(sql, params) return results } catch (error) { console.error(SQL执行错误:, error) throw error } } module.exports { createConnection, query, pool }4.3 用户认证模块routes/auth.jsconst express require(express) const bcrypt require(bcryptjs) const jwt require(jsonwebtoken) const { query } require(../config/database) const { validateLogin } require(../middleware/validation) const router express.Router() // 用户登录 router.post(/login, validateLogin, async (req, res) { try { const { username, password } req.body // 查询用户 const users await query( SELECT * FROM users WHERE username ? AND status 1, [username] ) if (users.length 0) { return res.status(401).json({ success: false, message: 用户名或密码错误 }) } const user users[0] // 验证密码 const isValidPassword await bcrypt.compare(password, user.password) if (!isValidPassword) { return res.status(401).json({ success: false, message: 用户名或密码错误 }) } // 生成JWT token const token jwt.sign( { userId: user.id, username: user.username, role: user.role }, process.env.JWT_SECRET || your-secret-key, { expiresIn: 24h } ) res.json({ success: true, message: 登录成功, data: { token, userInfo: { id: user.id, username: user.username, email: user.email, role: user.role } } }) } catch (error) { console.error(登录错误:, error) res.status(500).json({ success: false, message: 登录失败请稍后重试 }) } }) // 获取用户信息 router.get(/profile, async (req, res) { try { const token req.headers.authorization?.replace(Bearer , ) if (!token) { return res.status(401).json({ success: false, message: 未提供认证令牌 }) } const decoded jwt.verify(token, process.env.JWT_SECRET || your-secret-key) const users await query( SELECT id, username, email, role, created_at FROM users WHERE id ? AND status 1, [decoded.userId] ) if (users.length 0) { return res.status(404).json({ success: false, message: 用户不存在 }) } const user users[0] // 获取用户菜单权限 const menus await query( SELECT m.* FROM menus m INNER JOIN role_menus rm ON m.id rm.menu_id WHERE rm.role ? AND m.status 1 ORDER BY m.sort ASC , [user.role]) res.json({ success: true, data: { userInfo: user, menus: menus } }) } catch (error) { if (error.name JsonWebTokenError) { return res.status(401).json({ success: false, message: 认证令牌无效 }) } console.error(获取用户信息错误:, error) res.status(500).json({ success: false, message: 获取用户信息失败 }) } }) module.exports router4.4 用户管理模块routes/users.jsconst express require(express) const bcrypt require(bcryptjs) const { query } require(../config/database) const { authMiddleware, adminMiddleware } require(../middleware/auth) const router express.Router() // 获取用户列表 router.get(/, authMiddleware, adminMiddleware, async (req, res) { try { const { page 1, pageSize 10, username } req.query const offset (page - 1) * pageSize let whereClause WHERE status 1 let params [] if (username) { whereClause AND username LIKE ? params.push(%${username}%) } // 查询总数 const countResult await query( SELECT COUNT(*) as total FROM users ${whereClause}, params ) // 查询用户列表 const users await query( SELECT id, username, email, role, status, created_at, updated_at FROM users ${whereClause} ORDER BY created_at DESC LIMIT ? OFFSET ?, [...params, parseInt(pageSize), offset] ) res.json({ success: true, data: { list: users, pagination: { page: parseInt(page), pageSize: parseInt(pageSize), total: countResult[0].total } } }) } catch (error) { console.error(获取用户列表错误:, error) res.status(500).json({ success: false, message: 获取用户列表失败 }) } }) // 创建用户 router.post(/, authMiddleware, adminMiddleware, async (req, res) { try { const { username, password, email, role user } req.body // 检查用户名是否已存在 const existingUsers await query( SELECT id FROM users WHERE username ?, [username] ) if (existingUsers.length 0) { return res.status(400).json({ success: false, message: 用户名已存在 }) } // 加密密码 const hashedPassword await bcrypt.hash(password, 10) const result await query( INSERT INTO users (username, password, email, role) VALUES (?, ?, ?, ?), [username, hashedPassword, email, role] ) res.json({ success: true, message: 用户创建成功, data: { id: result.insertId } }) } catch (error) { console.error(创建用户错误:, error) res.status(500).json({ success: false, message: 创建用户失败 }) } }) // 更新用户 router.put(/:id, authMiddleware, adminMiddleware, async (req, res) { try { const { id } req.params const { email, role, status } req.body const updateFields [] const params [] if (email ! undefined) { updateFields.push(email ?) params.push(email) } if (role ! undefined) { updateFields.push(role ?) params.push(role) } if (status ! undefined) { updateFields.push(status ?) params.push(status) } if (updateFields.length 0) { return res.status(400).json({ success: false, message: 没有提供更新字段 }) } params.push(id) await query( UPDATE users SET ${updateFields.join(, )} WHERE id ?, params ) res.json({ success: true, message: 用户更新成功 }) } catch (error) { console.error(更新用户错误:, error) res.status(500).json({ success: false, message: 更新用户失败 }) } }) module.exports router5. 前后端数据交互5.1 API请求封装utils/request.jsimport axios from axios import { ElMessage } from element-plus import { useUserStore } from /stores/user import router from /router // 创建axios实例 const request axios.create({ baseURL: process.env.VUE_APP_API_BASE_URL || http://localhost:3000/api, timeout: 10000 }) // 请求拦截器 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) { const { data } response if (data.success false) { ElMessage.error(data.message || 请求失败) return Promise.reject(new Error(data.message)) } return data }, (error) { if (error.response) { const { status, data } error.response switch (status) { case 401: ElMessage.error(认证失败请重新登录) const userStore useUserStore() userStore.logout() router.push(/login) break case 403: ElMessage.error(没有权限访问) break case 404: ElMessage.error(请求的资源不存在) break case 500: ElMessage.error(服务器内部错误) break default: ElMessage.error(data?.message || 请求失败) } } else if (error.request) { ElMessage.error(网络错误请检查网络连接) } else { ElMessage.error(请求配置错误) } return Promise.reject(error) } ) export default request5.2 用户管理页面实现UserManagement.vuetemplate div classuser-management el-card template #header div classcard-header span用户管理/span el-button typeprimary clickhandleCreate el-iconPlus //el-icon 新增用户 /el-button /div /template !-- 搜索区域 -- div classsearch-area el-form :modelsearchForm inline el-form-item label用户名 el-input v-modelsearchForm.username placeholder请输入用户名 clearable / /el-form-item el-form-item el-button typeprimary clickhandleSearch el-iconSearch //el-icon 搜索 /el-button el-button clickhandleReset el-iconRefresh //el-icon 重置 /el-button /el-form-item /el-form /div !-- 用户表格 -- el-table :datauserList v-loadingloading border stripe el-table-column propid labelID width80 / el-table-column propusername label用户名 / el-table-column propemail label邮箱 / el-table-column proprole label角色 template #default{ row } el-tag :typerow.role admin ? danger : primary {{ row.role admin ? 管理员 : 普通用户 }} /el-tag /template /el-table-column el-table-column propstatus label状态 template #default{ row } el-tag :typerow.status ? success : danger {{ row.status ? 启用 : 禁用 }} /el-tag /template /el-table-column el-table-column propcreated_at label创建时间 / el-table-column label操作 width200 template #default{ row } el-button sizesmall clickhandleEdit(row) 编辑 /el-button el-button sizesmall typedanger clickhandleDelete(row) 删除 /el-button /template /el-table-column /el-table !-- 分页 -- div classpagination el-pagination v-model:current-pagepagination.page v-model:page-sizepagination.pageSize :totalpagination.total :page-sizes[10, 20, 50, 100] layouttotal, sizes, prev, pager, next, jumper size-changehandleSizeChange current-changehandleCurrentChange / /div /el-card !-- 新增/编辑用户对话框 -- user-dialog v-modeldialogVisible :usercurrentUser :modedialogMode successhandleDialogSuccess / /div /template script setup import { ref, reactive, onMounted } from vue import { ElMessage, ElMessageBox } from element-plus import { getUsersAPI, deleteUserAPI } from /api/user import UserDialog from ./components/UserDialog.vue const loading ref(false) const userList ref([]) const dialogVisible ref(false) const dialogMode ref(create) const currentUser ref(null) const searchForm reactive({ username: }) const pagination reactive({ page: 1, pageSize: 10, total: 0 }) // 获取用户列表 const fetchUsers async () { try { loading.value true const response await getUsersAPI({ page: pagination.page, pageSize: pagination.pageSize, ...searchForm }) userList.value response.data.list pagination.total response.data.pagination.total } catch (error) { console.error(获取用户列表失败:, error) } finally { loading.value false } } // 搜索用户 const handleSearch () { pagination.page 1 fetchUsers() } // 重置搜索 const handleReset () { Object.keys(searchForm).forEach(key { searchForm[key] }) pagination.page 1 fetchUsers() } // 新增用户 const handleCreate () { dialogMode.value create currentUser.value null dialogVisible.value true } // 编辑用户 const handleEdit (user) { dialogMode.value edit currentUser.value { ...user } dialogVisible.value true } // 删除用户 const handleDelete async (user) { try { await ElMessageBox.confirm( 确定要删除用户${user.username}吗此操作不可恢复。, 确认删除, { confirmButtonText: 确定, cancelButtonText: 取消, type: warning } ) await deleteUserAPI(user.id) ElMessage.success(删除成功) fetchUsers() } catch (error) { if (error ! cancel) { ElMessage.error(删除失败) } } } // 分页大小改变 const handleSizeChange (size) { pagination.pageSize size pagination.page 1 fetchUsers() } // 当前页改变 const handleCurrentChange (page) { pagination.page page fetchUsers() } // 对话框操作成功 const handleDialogSuccess () { dialogVisible.value false fetchUsers() } onMounted(() { fetchUsers() }) /script style scoped .card-header { display: flex; justify-content: space-between; align-items: center; } .search-area { margin-bottom: 20px; } .pagination { margin-top: 20px; display: flex; justify-content: flex-end; } /style6. 系统部署与优化6.1 生产环境配置创建环境配置文件// .env.production NODE_ENVproduction PORT3000 DB_HOSTlocalhost DB_PORT3306 DB_USERyour_production_db_user DB_PASSWORDyour_production_db_password DB_NAMEvue3_admin_prod JWT_SECRETyour-production-jwt-secret FRONTEND_URLhttps://your-domain.com前端构建配置vite.config.jsimport { defineConfig } from vite import vue from vitejs/plugin-vue import { resolve } from path export default defineConfig(({ mode }) { return { plugins: [vue()], resolve: { alias: { : resolve(__dirname, src) } }, server: { port: 3000, proxy: { /api: { target: http://localhost:3001, changeOrigin: true } } }, build: { outDir: dist, sourcemap: mode development, chunkSizeWarningLimit: 600, rollupOptions: { output: { manualChunks: { element-plus: [element-plus], echarts: [echarts], vendor: [vue, vue-router, pinia, axios] } } } } } })6.2 性能优化建议前端优化组件懒加载使用Vue Router的懒加载功能图片优化使用WebP格式实现懒加载代码分割合理配置Webpack/Vite的代码分割策略CDN加速静态
上一篇/下一篇内容由系统自动关联 返回资讯列表 →