1. 概述
Vue-Element-Admin 是一个基于 Vue.js 和 Element UI 的企业级中后台管理系统模板,提供了一套完整的前端解决方案。它具有丰富的功能组件、完善的权限管理、优雅的界面设计和良好的开发体验,是构建企业级管理系统的理想选择。
官方文档:https://panjiachen.github.io/vue-element-admin-site/zh/guide/
2. 核心技术栈
| 技术/库 | 版本 | 用途 |
|---|---|---|
| Vue | 2.6.10 | 前端框架 |
| Vue Router | 3.0.2 | 路由管理 |
| Vuex | 3.1.0 | 状态管理 |
| Element UI | 2.13.2 | UI 组件库 |
| Axios | 0.18.1 | 网络请求 |
| ECharts | 4.2.1 | 数据可视化 |
| Sass | 1.26.2 | CSS 预处理器 |
| Webpack | 4.x | 构建工具 |
| Jest | 23.6.0 | 单元测试 |
3. 架构设计
3.1 整体架构
Vue-Element-Admin 采用经典的 Vue 单页应用架构,结合 Element UI 组件库,构建了一套完整的中后台管理系统解决方案。整体架构遵循了模块化、组件化的设计理念,通过清晰的分层结构,实现了代码的高可维护性和可扩展性。
架构层次:
- 表现层:由 Vue 组件构成,负责 UI 渲染和用户交互
- 业务逻辑层:处理业务逻辑,包括状态管理、路由控制等
- 数据层:负责数据的获取、存储和处理
- 工具层:提供通用工具函数和辅助类
3.2 目录结构
src/
├── api/ # API 接口定义
├── assets/ # 静态资源
├── components/ # 通用组件
├── directive/ # 自定义指令
├── filters/ # 过滤器
├── icons/ # SVG 图标
├── layout/ # 布局组件
├── router/ # 路由配置
├── store/ # 状态管理
├── styles/ # 全局样式
├── utils/ # 工具函数
├── views/ # 页面视图
├── App.vue # 根组件
├── main.js # 入口文件
├── permission.js # 权限控制
└── settings.js # 系统设置
目录职责说明:
- api/:集中管理 API 接口定义,按业务模块划分
- components/:存放可复用的通用组件
- layout/:应用布局组件,包括侧边栏、顶部导航等
- router/:路由配置,包括常量路由和动态路由
- store/:Vuex 状态管理,按功能模块划分
- views/:页面视图,按业务模块组织
- utils/:工具函数,如请求封装、权限处理等
3.3 核心模块关系
4. 组件系统
4.1 布局组件
布局组件是整个应用的骨架,位于 src/layout/ 目录下,主要包括:
- Layout:根布局组件,整合了侧边栏、顶部导航、标签页和主内容区
- Sidebar:侧边栏组件,显示导航菜单
- Navbar:顶部导航栏,显示用户信息、全局搜索等
- TagsView:标签页组件,记录浏览历史
- AppMain:主内容区,显示路由对应的页面
布局组件示例:
<template>
<div :class="classObj" class="app-wrapper">
<div v-if="device==='mobile'&&sidebar.opened" class="drawer-bg" @click="handleClickOutside" />
<sidebar class="sidebar-container" />
<div :class="{hasTagsView:needTagsView}" class="main-container">
<div :class="{'fixed-header':fixedHeader}">
<navbar />
<tags-view v-if="needTagsView" />
</div>
<app-main />
<right-panel v-if="showSettings">
<settings />
</right-panel>
</div>
</div>
</template>
4.2 业务组件
业务组件是与具体业务逻辑相关的组件,位于 src/views/ 目录下,按业务模块组织。例如:
- dashboard:仪表盘
- permission:权限管理
- table:表格展示
- form:表单处理
- charts:数据可视化
4.3 通用组件
通用组件是可复用的基础组件,位于 src/components/ 目录下,包括:
- BackToTop:回到顶部
- Breadcrumb:面包屑导航
- Charts:图表组件
- ErrorLog:错误日志
- Pagination:分页组件
- SvgIcon:SVG 图标
- Tinymce:富文本编辑器
- Upload:文件上传
通用组件示例 - SvgIcon:
<template>
<div v-if="isExternal" :style="styleExternalIcon" class="svg-external-icon svg-icon" v-on="$listeners" />
<svg v-else :class="svgClass" aria-hidden="true" v-on="$listeners">
<use :xlink:href="iconName" />
</svg>
</template>
<script>
export default {
name: 'SvgIcon',
props: {
iconClass: {
type: String,
required: true
},
className: {
type: String,
default: ''
}
},
computed: {
isExternal() {
return /^(https?:|mailto:|tel:)/.test(this.iconClass)
},
iconName() {
return `#icon-${this.iconClass}`
},
svgClass() {
if (this.className) {
return `svg-icon ${this.className}`
} else {
return 'svg-icon'
}
},
styleExternalIcon() {
return {
mask: `url(https://blog.csdn.net/%3C/span%3E%3Cspan%20class=%22token%20interpolation%22%3E%3Cspan%20class=%22token%20interpolation-punctuation%20punctuation%22%3E$%7B%3C!--%20--%3E%3C/span%3E%3Cspan%20class=%22token%20keyword%22%3Ethis%3C/span%3E%3Cspan%20class=%22token%20punctuation%22%3E.%3C/span%3EiconClass%3Cspan%20class=%22token%20interpolation-punctuation%20punctuation%22%3E%7D%3C/span%3E%3C/span%3E%3Cspan%20class=%22token%20string%22%3E) no-repeat 50% 50%`,
'-webkit-mask': `url(https://blog.csdn.net/%3C/span%3E%3Cspan%20class=%22token%20interpolation%22%3E%3Cspan%20class=%22token%20interpolation-punctuation%20punctuation%22%3E$%7B%3C!--%20--%3E%3C/span%3E%3Cspan%20class=%22token%20keyword%22%3Ethis%3C/span%3E%3Cspan%20class=%22token%20punctuation%22%3E.%3C/span%3EiconClass%3Cspan%20class=%22token%20interpolation-punctuation%20punctuation%22%3E%7D%3C/span%3E%3C/span%3E%3Cspan%20class=%22token%20string%22%3E) no-repeat 50% 50%`
}
}
}
}
</script>
4.4 组件通信方式
Vue-Element-Admin 采用多种组件通信方式,根据不同场景选择合适的通信方式:
- Props/Events:父组件向子组件传递数据,子组件通过事件向父组件传递消息
- Vuex:全局状态管理,适用于跨组件、跨层级的状态共享
- EventBus:事件总线,适用于非父子关系组件间的通信
- Provide/Inject:祖先组件向后代组件注入依赖,适用于深层级组件通信
5. 状态管理
5.1 Vuex 架构
Vue-Element-Admin 使用 Vuex 进行状态管理,采用模块化的架构设计,将不同功能的状态分离到不同的模块中,提高了代码的可维护性和可扩展性。
核心概念:
- State:存储状态
- Getters:计算属性,用于派生状态
- Mutations:修改状态的方法,必须是同步的
- Actions:异步操作,可包含多个 mutations
- Modules:模块化管理状态
5.2 模块划分
Vuex 模块位于 src/store/modules/ 目录下,主要包括:
- app:应用配置,如侧边栏状态、设备类型等
- user:用户信息,如登录状态、权限等
- permission:权限管理,如动态路由等
- settings:系统设置,如主题、布局等
- tagsView:标签页状态
- errorLog:错误日志
模块示例 - user.js:
import { login, logout, getInfo } from '@/api/user'
import { getToken, setToken, removeToken } from '@/utils/auth'
import { resetRouter } from '@/router'
const getDefaultState = () => {
return {
token: getToken(),
name: '',
avatar: '',
roles: []
}
}
const state = getDefaultState()
const mutations = {
RESET_STATE: (state) => {
Object.assign(state, getDefaultState())
},
SET_TOKEN: (state, token) => {
state.token = token
},
SET_NAME: (state, name) => {
state.name = name
},
SET_AVATAR: (state, avatar) => {
state.avatar = avatar
},
SET_ROLES: (state, roles) => {
state.roles = roles
}
}
const actions = {
// user login
login({ commit }, userInfo) {
const { username, password } = userInfo
return new Promise((resolve, reject) => {
login({ username: username.trim(), password: password }).then(response => {
const { data } = response
commit('SET_TOKEN', data.token)
setToken(data.token)
resolve()
}).catch(error => {
reject(error)
})
})
},
// get user info
getInfo({ commit, state }) {
return new Promise((resolve, reject) => {
getInfo(state.token).then(response => {
const { data } = response
if (!data) {
reject('Verification failed, please Login again.')
}
const { roles, name, avatar } = data
// roles must be a non-empty array
if (!roles || roles.length <= 0) {
reject('getInfo: roles must be a non-null array!')
}
commit('SET_ROLES', roles)
commit('SET_NAME', name)
commit('SET_AVATAR', avatar)
resolve(data)
}).catch(error => {
reject(error)
})
})
},
// user logout
logout({ commit, state }) {
return new Promise((resolve, reject) => {
logout(state.token).then(() => {
removeToken() // must remove token first
resetRouter()
commit('RESET_STATE')
resolve()
}).catch(error => {
reject(error)
})
})
},
// remove token
resetToken({ commit }) {
return new Promise(resolve => {
removeToken() // must remove token first
commit('RESET_STATE')
resolve()
})
}
}
export default {
namespaced: true,
state,
mutations,
actions
}
5.3 状态管理最佳实践
- 模块化:按功能划分模块,提高代码可维护性
- 命名空间:使用
namespaced: true避免命名冲突 - 异步操作:将异步操作放在 actions 中, mutations 只处理同步操作
- 状态持久化:使用 localStorage 或 sessionStorage 持久化关键状态
- 计算属性:使用 getters 派生复杂状态,提高代码复用性
6. 路由机制
6.1 路由配置
Vue-Element-Admin 使用 Vue Router 进行路由管理,路由配置位于 src/router/ 目录下,分为常量路由和动态路由两部分:
- constantRoutes:常量路由,所有用户都可以访问的路由
- asyncRoutes:动态路由,根据用户权限动态加载的路由
路由配置示例:
// 常量路由
export const constantRoutes = [
{
path: '/redirect',
component: Layout,
hidden: true,
children: [
{
path: '/redirect/:path(.*)',
component: () => import('@/views/redirect/index')
}
]
},
{
path: '/login',
component: () => import('@/views/login/index'),
hidden: true
},
// ... 其他常量路由
]
// 动态路由
export const asyncRoutes = [
{
path: '/permission',
component: Layout,
redirect: '/permission/page',
alwaysShow: true,
name: 'Permission',
meta: {
title: 'Permission',
icon: 'lock',
roles: ['admin', 'editor'] // 权限控制
},
children: [
{
path: 'page',
component: () => import('@/views/permission/page'),
name: 'PagePermission',
meta: {
title: 'Page Permission',
roles: ['admin'] // 更细粒度的权限控制
}
},
// ... 其他子路由
]
},
// ... 其他动态路由
]
6.2 动态路由与权限控制
Vue-Element-Admin 实现了基于角色的权限控制,通过以下步骤实现:
- 用户登录后获取角色信息
- 根据角色信息动态生成可访问的路由
- 使用
router.addRoutes()方法添加动态路由 - 实现页面级、按钮级的权限控制
权限控制示例:
// src/permission.js
import router from './router'
import store from './store'
import { Message } from 'element-ui'
import NProgress from 'nprogress' // progress bar
import 'nprogress/nprogress.css' // progress bar style
import { getToken } from '@/utils/auth' // get token from cookie
import getPageTitle from '@/utils/get-page-title'
NProgress.configure({ showSpinner: false })
const whiteList = ['/login', '/auth-redirect'] // no redirect whitelist
router.beforeEach(async(to, from, next) => {
// start progress bar
NProgress.start()
// set page title
document.title = getPageTitle(to.meta.title)
// determine whether the user has logged in
const hasToken = getToken()
if (hasToken) {
if (to.path === '/login') {
// if is logged in, redirect to the home page
next({ path: '/' })
NProgress.done()
} else {
// determine whether the user has obtained his permission roles through getInfo
const hasRoles = store.getters.roles && store.getters.roles.length > 0
if (hasRoles) {
next()
} else {
try {
// get user info
// note: roles must be a object array! such as: ['admin'] or ,['developer','editor']
const { roles } = await store.dispatch('user/getInfo')
// generate accessible routes map based on roles
const accessRoutes = await store.dispatch('permission/generateRoutes', roles)
// dynamically add accessible routes
router.addRoutes(accessRoutes)
// hack method to ensure that addRoutes is complete
// set the replace: true, so the navigation will not leave a history record
next({ ...to, replace: true })
} catch (error) {
// remove token and go to login page to re-login
await store.dispatch('user/resetToken')
Message.error(error || 'Has Error')
next(`/login?redirect=${to.path}`)
NProgress.done()
}
}
}
} else {
/* has no token */
if (whiteList.indexOf(to.path) !== -1) {
// in the free login whitelist, go directly
next()
} else {
// other pages that do not have permission to access are redirected to the login page
next(`/login?redirect=${to.path}`)
NProgress.done()
}
}
})
router.afterEach(() => {
// finish progress bar
NProgress.done()
})
6.3 路由守卫
Vue-Element-Admin 使用路由守卫实现了以下功能:
- 全局前置守卫:处理登录状态、权限验证、页面标题设置等
- 全局后置守卫:处理页面加载进度条等
7. API 设计与网络请求
7.1 Axios 封装
Vue-Element-Admin 对 Axios 进行了封装,提供了统一的请求处理机制,位于 src/utils/request.js 文件中。
封装示例:
import axios from 'axios'
import { MessageBox, Message } from 'element-ui'
import store from '@/store'
import { getToken } from '@/utils/auth'
// create an axios instance
const service = axios.create({
baseURL: process.env.VUE_APP_BASE_API, // url = base url + request url
timeout: 5000 // request timeout
})
// request interceptor
service.interceptors.request.use(
config => {
// do something before request is sent
if (store.getters.token) {
// let each request carry token
config.headers['X-Token'] = getToken()
}
return config
},
error => {
// do something with request error
console.log(error) // for debug
return Promise.reject(error)
}
)
// response interceptor
service.interceptors.response.use(
response => {
const res = response.data
// if the custom code is not 20000, it is judged as an error.
if (res.code !== 20000) {
Message({
message: res.message || 'Error',
type: 'error',
duration: 5 * 1000
})
// 50008: Illegal token; 50012: Other clients logged in; 50014: Token expired;
if (res.code === 50008 || res.code === 50012 || res.code === 50014) {
// to re-login
MessageBox.confirm('You have been logged out, you can cancel to stay on this page, or log in again', 'Confirm logout', {
confirmButtonText: 'Re-Login',
cancelButtonText: 'Cancel',
type: 'warning'
}).then(() => {
store.dispatch('user/resetToken').then(() => {
location.reload()
})
})
}
return Promise.reject(new Error(res.message || 'Error'))
} else {
return res
}
},
error => {
console.log('err' + error) // for debug
Message({
message: error.message,
type: 'error',
duration: 5 * 1000
})
return Promise.reject(error)
}
)
export default service
7.2 API 模块化管理
Vue-Element-Admin 将 API 接口按业务模块进行划分,位于 src/api/ 目录下,每个文件对应一个业务模块。
API 模块示例 - user.js:
import request from '@/utils/request'
export function login(data) {
return request({
url: '/user/login',
method: 'post',
data
})
}
export function getInfo(token) {
return request({
url: '/user/info',
method: 'get',
params: { token }
})
}
export function logout() {
return request({
url: '/user/logout',
method: 'post'
})
}
7.3 请求/响应拦截器
Vue-Element-Admin 使用 Axios 拦截器实现了以下功能:
- 请求拦截器:统一添加认证 token、处理请求参数等
- 响应拦截器:统一处理错误响应、处理 token 过期等
8. 性能优化
8.1 代码分割与懒加载
概念定义:代码分割是将代码分成多个小块,懒加载是在需要时才加载这些小块,减少初始加载时间。
核心原理:使用 ES6 的动态导入语法 () => import() 实现代码分割和懒加载。
应用场景:适用于大型应用,特别是包含多个路由和大型组件的应用。
示例代码:
路由懒加载
{
path: 'dashboard',
component: () => import('@/views/dashboard/index'),
name: 'Dashboard',
meta: { title: 'Dashboard', icon: 'dashboard', affix: true }
}
组件懒加载
<template>
<div>
<h1>首页</h1>
<button @click="loadComponent">加载大型组件</button>
<component v-if="showComponent" :is="largeComponent" />
</div>
</template>
<script>
export default {
data() {
return {
showComponent: false,
largeComponent: null
}
},
methods: {
async loadComponent() {
const { default: LargeComponent } = await import('@/components/LargeComponent.vue')
this.largeComponent = LargeComponent
this.showComponent = true
}
}
}
</script>
第三方库按需引入
// 按需引入 Element UI 组件
import { Button, Input, Table } from 'element-ui'
import 'element-ui/lib/theme-chalk/index.css'
Vue.use(Button)
Vue.use(Input)
Vue.use(Table)
8.2 缓存策略
8.2.1 组件缓存
使用 Vue 的 <keep-alive> 组件缓存组件
<template>
<div class="app-main">
<keep-alive :include="cachedViews">
<router-view :key="key" />
</keep-alive>
</div>
</template>
<script>
export default {
computed: {
cachedViews() {
return this.$store.state.tagsView.cachedViews
},
key() {
return this.$route.path
}
}
}
</script>
8.2.2 路由缓存控制
// 路由配置中设置 noCache 元信息
{
path: 'list',
component: () => import('@/views/example/list'),
name: 'ArticleList',
meta: {
title: 'Article List',
icon: 'list',
noCache: true // 不缓存此页面
}
}
8.2.3 数据缓存
// src/utils/storage.js
import Cookies from 'js-cookie'
const dataCache = {
// 缓存数据
set(key, value, expire = 7) {
Cookies.set(key, value, { expires: expire })
},
// 获取缓存数据
get(key) {
return Cookies.get(key)
},
// 删除缓存数据
remove(key) {
Cookies.remove(key)
}
}
export default dataCache
// 使用示例
import dataCache from '@/utils/storage'
// 缓存用户信息
dataCache.set('userInfo', userInfo)
// 获取缓存的用户信息
const userInfo = dataCache.get('userInfo')
8.2.4 防抖和节流
概念定义:防抖是在事件触发后等待一段时间再执行,节流是限制事件执行的频率。
核心原理:使用定时器控制函数执行的时机和频率。
应用场景:适用于频繁触发的事件,如滚动、输入、 resize 等。
示例代码:
// src/utils/index.js
// 防抖
export function debounce(func, wait) {
let timeout
return function() {
const context = this
const args = arguments
clearTimeout(timeout)
timeout = setTimeout(() => {
func.apply(context, args)
}, wait)
}
}
// 节流
export function throttle(func, wait) {
let lastTime = 0
return function() {
const now = Date.now()
if (now - lastTime >= wait) {
func.apply(this, arguments)
lastTime = now
}
}
}
// 使用示例
import { debounce, throttle } from '@/utils'
// 防抖处理搜索输入
const handleSearch = debounce((keyword) => {
// 搜索逻辑
console.log('搜索:', keyword)
}, 300)
// 节流处理滚动事件
const handleScroll = throttle(() => {
// 滚动逻辑
console.log('滚动事件触发')
}, 100)
9. 安全性
9.1 权限控制
概念定义:权限控制是指限制用户对系统资源的访问权限,确保用户只能访问其有权限的资源。
核心原理:基于角色的权限控制(RBAC),通过动态路由生成和指令控制实现。
应用场景:适用于需要不同权限级别用户的系统,如管理员、编辑、普通用户等。
示例代码:
页面权限控制
<template>
<div>
<h1>管理员页面</h1>
<div v-if="hasPermission('admin')">
<!-- 管理员才能看到的内容 -->
<button @click="deleteUser">删除用户</button>
</div>
<div v-else>
<p>您没有权限访问此功能</p>
</div>
</div>
</template>
<script>
import { mapGetters } from 'vuex'
export default {
computed: {
...mapGetters(['roles']),
hasPermission(role) {
return this.roles.includes(role)
}
},
methods: {
deleteUser() {
// 删除用户逻辑
}
}
}
</script>
按钮权限控制
<template>
<div>
<button v-permission="['admin']" @click="deleteUser">删除用户</button>
<button v-permission="['admin', 'editor']" @click="editUser">编辑用户</button>
<button @click="viewUser">查看用户</button>
</div>
</template>
<script>
export default {
methods: {
deleteUser() {
// 删除用户逻辑
},
editUser() {
// 编辑用户逻辑
},
viewUser() {
// 查看用户逻辑
}
}
}
</script>
2653

被折叠的 条评论
为什么被折叠?



