Vue3 后台管理系统完整开发指南
引言
本指南将详细介绍如何使用Vue3创建一个完整的后台管理系统,包括登录注册、用户管理、角色管理、菜单管理等功能,并对接后端接口。我们将基于Vue3 + Vite + TypeScript + Element Plus + Pinia等主流技术栈进行开发,确保系统具有高性能和可维护性。
项目初始化
环境准备
首先,我们需要确保环境已经配置好:
- Node.js 16.18.0 以上版本
- pnpm 8.6.0 以上版本(推荐使用pnpm)
初始化项目
使用Vite初始化一个Vue3项目:
npm init vite@latest vue3-admin --template vue-ts
这将创建一个基于Vue3和TypeScript的项目模板。
项目结构
项目的基本结构如下:
src/
├── api/ # API请求
├── assets/ # 资源文件
├── components/ # 公共组件
├── hooks/ # 组合式函数
├── layout/ # 布局组件
├── models/ # TypeScript类型定义
├── router/ # 路由配置
├── store/ # Pinia状态管理
├── styles/ # 全局样式
├── utils/ # 工具函数
└── views/ # 页面组件
技术栈选择
我们将使用以下技术栈:
- Vue3:渐进式JavaScript框架
- Vite:开发和构建工具
- TypeScript:JavaScript的超集
- Element Plus:基于Vue3的组件库
- Pinia:Vue的状态管理库
- Axios:HTTP客户端
- UnoCSS:原子CSS库
项目配置
路径别名配置
为了简化路径引用,我们在vite.config.ts
中配置路径别名:
// vite.config.ts
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
export default defineConfig({
plugins: [vue()],
resolve: {
alias: {
'@': '/src'
}
}
})
TypeScript配置
在tsconfig.json
中添加路径映射:
{
"compilerOptions": {
"baseUrl": "./",
"paths": {
"@/*": ["src/*"]
}
}
}
自动导入配置
使用unplugin实现API和组件的自动导入:
npm install -D unplugin-auto-import unplugin-vue-components
在vite.config.ts
中添加自动导入配置:
// vite.config.ts
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: [
AutoImport({
imports: ['vue', 'vue-router', '@/store', '@/api'],
eslintrc: {
enabled: true,
filepath: './.eslintrc-auto-import.json'
}
}),
Components({
resolvers: [ElementPlusResolver()]
})
]
})
集成Element Plus
Element Plus是一个功能丰富的UI组件库,我们将使用它来构建管理界面。 安装Element Plus:
npm install element-plus
在main.ts
中引入Element Plus:
import 'element-plus'
路由配置
使用Vue Router实现动态路由。我们需要定义静态路由和异步路由。 静态路由(不需要权限的路由):
// src/router/constantRoutes.ts
import { createRouter, createWebHashHistory, RouteRecordRaw } from 'vue-router'
const LAYOUT = () => import('@/layout/index.vue')
export const constantRoutes: RouteRecordRaw[] = [
{
path: '/login',
component: () => import('@/views/login/index.vue'),
meta: { hidden: true }
},
{
path: '/',
component: LAYOUT,
redirect: '/dashboard',
children: [
{
path: 'dashboard',
component: () => import('@/views/dashboard/index.vue'),
name: 'Dashboard',
meta: { title: '仪表盘', icon: 'dashboard', affix: true }
}
]
}
]
异步路由(需要权限的路由):
// src/router/dynamicRoutes.ts
import { createRouter, createWebHashHistory, RouteRecordRaw } from 'vue-router'
const LAYOUT = () => import('@/layout/index.vue')
export const dynamicRoutes: RouteRecordRaw[] = [
{
name: 'System',
path: '/system',
component: LAYOUT,
meta: { title: '系统管理', icon: 'system' },
children: [
{
name: 'User',
path: '/system/user',
component: () => import('@/views/system/user/index.vue'),
meta: { title: '用户管理', icon: 'user' }
},
{
name: 'Role',
path: '/system/role',
component: () => import('@/views/system/role/index.vue'),
meta: { title: '角色管理', icon: 'role' }
},
{
name: 'Menu',
path: '/system/menu',
component: () => import('@/views/system/menu/index.vue'),
meta: { title: '菜单管理', icon: 'menu' }
}
]
}
]
Pinia状态管理
Pinia是Vue3的状态管理库,我们将使用它来管理应用程序的状态。 定义用户Store:
// src/store/user.ts
import { defineStore } from 'pinia'
export const useUserStore = defineStore('user', {
state: () => ({
token: '',
userInfo: null as any
}),
actions: {
setToken(token: string) {
this.token = token
},
setUserInfo(userInfo: any) {
this.userInfo = userInfo
}
}
})
定义权限Store:
// src/store/permission.ts
import { defineStore } from 'pinia'
export const usePermissionStore = defineStore('permission', {
state: () => ({
routes: [] as RouteRecordRaw[],
buttons: [] as string[]
}),
actions: {
setAuth({ routes, buttons }: { routes: string[], buttons: string[] }) {
this.routes = routes
this.buttons = buttons
}
}
})
登录注册功能
登录页面
创建登录页面组件:
<!-- src/views/login/index.vue -->
<template>
<div class="login-container">
<el-form ref="formEl" :model="ruleForm" :rules="rules">
<el-form-item prop="username">
<el-input v-model="ruleForm.username" placeholder="用户名" />
</el-form-item>
<el-form-item prop="password">
<el-input v-model="ruleForm.password" type="password" placeholder="密码" />
</el-form-item>
<el-button type="primary" @click="submitForm(formEl)">登录</el-button>
</el-form>
</div>
</template>
<script setup lang="ts">
import { useUserStore } from '@/store/user'
import { useRouter } from 'vue-router'
import { login } from '@/api/auth'
const userStore = useUserStore()
const router = useRouter()
const ruleForm = ref({
username: '',
password: ''
})
const rules = {
username: [{ required: true, message: '请输入用户名', trigger: 'blur' }],
password: [{ required: true, message: '请输入密码', trigger: 'blur' }]
}
const formEl = ref<FormInstance>()
const submitForm = async (formEl: FormInstance | undefined) => {
if (!formEl) return
try {
const { data } = await login(ruleForm.value)
userStore.setToken(data.token)
router.replace('/')
} catch (error) {
console.error(error)
}
}
</script>
注册页面
创建注册页面组件:
<!-- src/views/register/index.vue -->
<template>
<div class="register-container">
<el-form ref="formEl" :model="ruleForm" :rules="rules">
<el-form-item prop="username">
<el-input v-model="ruleForm.username" placeholder="用户名" />
</el-form-item>
<el-form-item prop="password">
<el-input v-model="ruleForm.password" type="password" placeholder="密码" />
</el-form-item>
<el-form-item prop="confirmPassword">
<el-input v-model="ruleForm.confirmPassword" type="password" placeholder="确认密码" />
</el-form-item>
<el-button type="primary" @click="submitForm(formEl)">注册</el-button>
</el-form>
</div>
</template>
<script setup lang="ts">
import { useUserStore } from '@/store/user'
import { useRouter } from 'vue-router'
import { register } from '@/api/auth'
const userStore = useUserStore()
const router = useRouter()
const ruleForm = ref({
username: '',
password: '',
confirmPassword: ''
})
const rules = {
username: [{ required: true, message: '请输入用户名', trigger: 'blur' }],
password: [{ required: true, message: '请输入密码', trigger: 'blur' }],
confirmPassword: [
{ required: true, message: '请输入确认密码', trigger: 'blur' },
{ validator: (rule, value) => value === ruleForm.value.password, message: '两次输入的密码不一致', trigger: 'blur' }
]
}
const formEl = ref<FormInstance>()
const submitForm = async (formEl: FormInstance | undefined) => {
if (!formEl) return
try {
await register(ruleForm.value)
router.replace('/login')
} catch (error) {
console.error(error)
}
}
</script>
权限管理
路由权限控制
在router/index.ts
中添加路由守卫:
// src/router/index.ts
import { createRouter, createWebHashHistory, RouteRecordRaw } from 'vue-router'
import { useUserStore } from '@/store/user'
import { usePermissionStore } from '@/store/permission'
import { login } from '@/api/auth'
import { constantRoutes, dynamicRoutes } from './routes'
const router = createRouter({
history: createWebHashHistory(),
routes: constantRoutes as RouteRecordRaw[],
scrollBehavior: () => ({ left: 0, top: 0 })
})
router.beforeEach(async (to, from, next) => {
const userStore = useUserStore()
const permissionStore = usePermissionStore()
if (to.path === '/login') {
if (userStore.token) {
next('/')
} else {
next()
}
return
}
if (!userStore.token) {
next({ path: '/login', query: { redirect: to.fullPath } })
return
}
if (!permissionStore.routes.length) {
try {
await initDynamicRouter()
next({ ...to, replace: true })
} catch (error) {
console.error(error)
next('/')
}
return
}
next()
})
async function initDynamicRouter() {
const userStore = useUserStore()
const permissionStore = usePermissionStore()
try {
const { data } = await login()
userStore.setToken(data.token)
userStore.setUserInfo(data.userInfo)
const routes = filterAsyncRoutes(dynamicRoutes, data.userInfo.routes)
router.addRoute('System', routes)
permissionStore.setAuth({ routes: data.userInfo.routes, buttons: data.userInfo.buttons })
} catch (error) {
console.error(error)
throw error
}
}
function filterAsyncRoutes(asyncRoutes: RouteRecordRaw[], authRoutes: string[]) {
return asyncRoutes.filter(route => {
if (!authRoutes.includes(route.name as string)) return false
if (route.children) {
route.children = filterAsyncRoutes(route.children, authRoutes)
}
return true
})
}
export default router
按钮权限控制
创建按钮权限指令:
// src/directives/auth.ts
import { Directive, DirectiveBinding } from 'vue'
import { usePermissionStore } from '@/store/permission'
export const auth: Directive = {
mounted(el: HTMLElement, binding: DirectiveBinding) {
const { value } = binding
const permissionStore = usePermissionStore()
const buttons = permissionStore.buttons
if (value instanceof Array && value.length) {
const hasPermission = value.every(item => buttons.includes(item))
if (!hasPermission) {
el.parentNode?.removeChild(el)
}
} else {
if (!buttons.includes(value as string)) {
el.parentNode?.removeChild(el)
}
}
}
}
在main.ts
中注册指令:
// src/main.ts
import App from './App.vue'
import router from './router'
import store from './store'
import directives from './directives'
createApp(App)
.use(router)
.use(store)
.use(directives)
.mount('#app')
用户管理
用户列表页面
创建用户列表页面:
<!-- src/views/system/user/index.vue -->
<template>
<div class="app-container">
<el-button type="primary" @click="handleCreate">新增用户</el-button>
<el-table :data="users" border>
<el-table-column prop="username" label="用户名" />
<el-table-column prop="email" label="邮箱" />
<el-table-column prop="role" label="角色" />
<el-table-column label="操作">
<template #default="{ row }">
<el-button type="primary" @click="handleEdit(row)">编辑</el-button>
<el-button type="danger" @click="handleDelete(row)">删除</el-button>
</template>
</el-table-column>
</el-table>
</div>
</template>
<script setup lang="ts">
import { userList, userCreate, userUpdate, userDelete } from '@/api/system/user'
const users = ref([])
async function fetchUsers() {
const { data } = await userList()
users.value = data
}
async function handleCreate() {
await userCreate()
await fetchUsers()
}
async function handleEdit(row: any) {
await userUpdate(row.id)
await fetchUsers()
}
async function handleDelete(row: any) {
await userDelete(row.id)
await fetchUsers()
}
fetchUsers()
</script>
用户详情页面
创建用户详情页面:
<!-- src/views/system/user/detail.vue -->
<template>
<div class="app-container">
<el-form ref="formEl" :model="user" :rules="rules">
<el-form-item label="用户名" prop="username">
<el-input v-model="user.username" />
</el-form-item>
<el-form-item label="邮箱" prop="email">
<el-input v-model="user.email" />
</el-form-item>
<el-form-item label="角色" prop="role">
<el-select v-model="user.role">
<el-option label="管理员" value="admin" />
<el-option label="普通用户" value="user" />
</el-select>
</el-form-item>
<el-button type="primary" @click="submitForm(formEl)">保存</el-button>
</el-form>
</div>
</template>
<script setup lang="ts">
import { defineProps, ref } from 'vue'
import { useRoute, useRouter } from 'vue-router'
import { userDetail, userUpdate } from '@/api/system/user'
const props = defineProps({
id: {
type: String,
required: true
}
})
const route = useRoute()
const router = useRouter()
const user = ref({})
const rules = {
username: [{ required: true, message: '请输入用户名', trigger: 'blur' }],
email: [{ required: true, message: '请输入邮箱', trigger: 'blur' }],
role: [{ required: true, message: '请选择角色', trigger: 'change' }]
}
const formEl = ref<FormInstance>()
async function fetchUser() {
const { data } = await userDetail(props.id)
user.value = data
}
async function submitForm(formEl: FormInstance | undefined) {
if (!formEl) return
try {
await userUpdate(props.id, user.value)
router.push('/system/user')
} catch (error) {
console.error(error)
}
}
fetchUser()
</script>
角色管理
角色列表页面
创建角色列表页面:
<!-- src/views/system/role/index.vue -->
<template>
<div class="app-container">
<el-button type="primary" @click="handleCreate">新增角色</el-button>
<el-table :data="roles" border>
<el-table-column prop="name" label="角色名称" />
<el-table-column prop="description" label="描述" />
<el-table-column label="操作">
<template #default="{ row }">
<el-button type="primary" @click="handleEdit(row)">编辑</el-button>
<el-button type="danger" @click="handleDelete(row)">删除</el-button>
</template>
</el-table-column>
</el-table>
</div>
</template>
<script setup lang="ts">
import { roleList, roleCreate, roleUpdate, roleDelete } from '@/api/system/role'
const roles = ref([])
async function fetchRoles() {
const { data } = await roleList()
roles.value = data
}
async function handleCreate() {
await roleCreate()
await fetchRoles()
}
async function handleEdit(row: any) {
await roleUpdate(row.id)
await fetchRoles()
}
async function handleDelete(row: any) {
await roleDelete(row.id)
await fetchRoles()
}
fetchRoles()
</script>
角色详情页面
创建角色详情页面:
<!-- src/views/system/role/detail.vue -->
<template>
<div class="app-container">
<el-form ref="formEl" :model="role" :rules="rules">
<el-form-item label="角色名称" prop="name">
<el-input v-model="role.name" />
</el-form-item>
<el-form-item label="描述" prop="description">
<el-input v-model="role.description" />
</el-form-item>
<el-button type="primary" @click="submitForm(formEl)">保存</el-button>
</el-form>
</div>
</template>
<script setup lang="ts">
import { defineProps, ref } from 'vue'
import { useRoute, useRouter } from 'vue-router'
import { roleDetail, roleUpdate } from '@/api/system/role'
const props = defineProps({
id: {
type: String,
required: true
}
})
const route = useRoute()
const router = useRouter()
const role = ref({})
const rules = {
name: [{ required: true, message: '请输入角色名称', trigger: 'blur' }]
}
const formEl = ref<FormInstance>()
async function fetchRole() {
const { data } = await roleDetail(props.id)
role.value = data
}
async function submitForm(formEl: FormInstance | undefined) {
if (!formEl) return
try {
await roleUpdate(props.id, role.value)
router.push('/system/role')
} catch (error) {
console.error(error)
}
}
fetchRole()
</script>
菜单管理
菜单列表页面
创建菜单列表页面:
<!-- src/views/system/menu/index.vue -->
<template>
<div class="app-container">
<el-button type="primary" @click="handleCreate">新增菜单</el-button>
<el-tree :data="menus" :props="{ children: 'children', label: 'title' }" />
</div>
</template>
<script setup lang="ts">
import { menuList, menuCreate, menuUpdate, menuDelete } from '@/api/system/menu'
const menus = ref([])
async function fetchMenus() {
const { data } = await menuList()
menus.value = data
}
async function handleCreate() {
await menuCreate()
await fetchMenus()
}
async function handleEdit(row: any) {
await menuUpdate(row.id)
await fetchMenus()
}
async function handleDelete(row: any) {
await menuDelete(row.id)
await fetchMenus()
}
fetchMenus()
</script>
菜单详情页面
创建菜单详情页面:
<!-- src/views/system/menu/detail.vue -->
<template>
<div class="app-container">
<el-form ref="formEl" :model="menu" :rules="rules">
<el-form-item label="菜单名称" prop="name">
<el-input v-model="menu.name" />
</el-form-item>
<el-form-item label="路由路径" prop="path">
<el-input v-model="menu.path" />
</el-form-item>
<el-form-item label="组件路径" prop="component">
<el-input v-model="menu.component" />
</el-form-item>
<el-form-item label="图标" prop="icon">
<el-input v-model="menu.icon" />
</el-form-item>
<el-button type="primary" @click="submitForm(formEl)">保存</el-button>
</el-form>
</div>
</template>
<script setup lang="ts">
import { defineProps, ref } from 'vue'
import { useRoute, useRouter } from 'vue-router'
import { menuDetail, menuUpdate } from '@/api/system/menu'
const props = defineProps({
id: {
type: String,
required: true
}
})
const route = useRoute()
const router = useRouter()
const menu = ref({})
const rules = {
name: [{ required: true, message: '请输入菜单名称', trigger: 'blur' }],
path: [{ required: true, message: '请输入路由路径', trigger: 'blur' }],
component: [{ required: true, message: '请输入组件路径', trigger: 'blur' }]
}
const formEl = ref<FormInstance>()
async function fetchMenu() {
const { data } = await menuDetail(props.id)
menu.value = data
}
async function submitForm(formEl: FormInstance | undefined) {
if (!formEl) return
try {
await menuUpdate(props.id, menu.value)
router.push('/system/menu')
} catch (error) {
console.error(error)
}
}
fetchMenu()
</script>
对接后端接口
API请求配置
在src/api
目录下创建API请求配置。例如,src/api/auth.ts
:
import { axios } from '@/utils/request'
export async function login() {
return axios.get('/api/login')
}
export async function register() {
return axios.post('/api/register')
}
接口文档
使用Swagger或OpenAPI工具创建接口文档,确保前后端接口一致。
总结
通过以上步骤,我们已经创建了一个完整的基于Vue3的后台管理系统,包括登录注册、用户管理、角色管理、菜单管理等功能,并对接了后端接口。这个系统使用了Vue3 + Vite + TypeScript + Element Plus + Pinia等技术栈,具有高性能和可维护性。 希望这个指南对你有所帮助!如果你有任何问题或建议,请随时反馈。