Vue-Element-Admin

1. 概述

Vue-Element-Admin 是一个基于 Vue.js 和 Element UI 的企业级中后台管理系统模板,提供了一套完整的前端解决方案。它具有丰富的功能组件、完善的权限管理、优雅的界面设计和良好的开发体验,是构建企业级管理系统的理想选择。
官方文档:https://panjiachen.github.io/vue-element-admin-site/zh/guide/

2. 核心技术栈

技术/库版本用途
Vue2.6.10前端框架
Vue Router3.0.2路由管理
Vuex3.1.0状态管理
Element UI2.13.2UI 组件库
Axios0.18.1网络请求
ECharts4.2.1数据可视化
Sass1.26.2CSS 预处理器
Webpack4.x构建工具
Jest23.6.0单元测试

3. 架构设计

3.1 整体架构

Vue-Element-Admin 采用经典的 Vue 单页应用架构,结合 Element UI 组件库,构建了一套完整的中后台管理系统解决方案。整体架构遵循了模块化、组件化的设计理念,通过清晰的分层结构,实现了代码的高可维护性和可扩展性。

架构层次:

  1. 表现层:由 Vue 组件构成,负责 UI 渲染和用户交互
  2. 业务逻辑层:处理业务逻辑,包括状态管理、路由控制等
  3. 数据层:负责数据的获取、存储和处理
  4. 工具层:提供通用工具函数和辅助类

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 核心模块关系

App.vue

Layout

Sidebar

Navbar

TagsView

AppMain

RouterView

Views

Router

Store

API

Utils

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 采用多种组件通信方式,根据不同场景选择合适的通信方式:

  1. Props/Events:父组件向子组件传递数据,子组件通过事件向父组件传递消息
  2. Vuex:全局状态管理,适用于跨组件、跨层级的状态共享
  3. EventBus:事件总线,适用于非父子关系组件间的通信
  4. 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 状态管理最佳实践

  1. 模块化:按功能划分模块,提高代码可维护性
  2. 命名空间:使用 namespaced: true 避免命名冲突
  3. 异步操作:将异步操作放在 actions 中, mutations 只处理同步操作
  4. 状态持久化:使用 localStorage 或 sessionStorage 持久化关键状态
  5. 计算属性:使用 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 实现了基于角色的权限控制,通过以下步骤实现:

  1. 用户登录后获取角色信息
  2. 根据角色信息动态生成可访问的路由
  3. 使用 router.addRoutes() 方法添加动态路由
  4. 实现页面级、按钮级的权限控制

权限控制示例:

// 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>
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值