24-前端核心技术-VueElementAdmin


学习目标
  1. 掌握vue-element-ui的使用方法

介绍

官网地址:https://panjiachen.github.io/vue-element-admin-site/zh/guide/

vue-element-admin 是一个后台前端解决方案,它基于 vue 和 element-ui实现。它使用了最新的前端技术栈,内置了 i18 国际化解决方案,动态路由,权限验证,提炼了典型的业务模型,提供了丰富的功能组件,可以快速搭建企业级中后台产品原型。

功能

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
- 登录 / 注销

- 权限验证
  - 页面权限
  - 指令权限
  - 权限配置
  - 二步登录

- 多环境发布
  - dev sit stage prod

- 全局功能
  - 国际化多语言
  - 多种动态换肤
  - 动态侧边栏(支持多级路由嵌套)
  - 动态面包屑
  - 快捷导航(标签页)
  - Svg Sprite 图标
  - 本地/后端 mock 数据
  - Screenfull全屏
  - 自适应收缩侧边栏

- 编辑器
  - 富文本
  - Markdown
  - JSON 等多格式

- Excel
  - 导出excel
  - 导入excel
  - 前端可视化excel
  - 导出zip

- 表格
  - 动态表格
  - 拖拽表格
  - 内联编辑

- 错误页面
  - 401
  - 404

- 組件
  - 头像上传
  - 返回顶部
  - 拖拽Dialog
  - 拖拽Select
  - 拖拽看板
  - 列表拖拽
  - SplitPane
  - Dropzone
  - Sticky
  - CountTo

- 综合实例
- 错误日志
- Dashboard
- 引导页
- ECharts 图表
- Clipboard(剪贴复制)
- Markdown2html

目录结构

本项目已经为你生成了一个完整的开发框架,提供了涵盖中后台开发的各类功能和坑位,下面是整个项目的目录结构。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
├── build                      # 构建相关
├── mock                       # 项目mock 模拟数据
├── plop-templates             # 基本模板
├── public                     # 静态资源
│   │── favicon.ico            # favicon图标
│   └── index.html             # html模板
├── src                        # 源代码
│   ├── api                    # 所有请求
│   ├── assets                 # 主题 字体等静态资源
│   ├── components             # 全局公用组件
│   ├── directive              # 全局指令
│   ├── filters                # 全局 filter
│   ├── icons                  # 项目所有 svg icons
│   ├── lang                   # 国际化 language
│   ├── layout                 # 全局 layout
│   ├── router                 # 路由
│   ├── store                  # 全局 store管理
│   ├── styles                 # 全局样式
│   ├── utils                  # 全局公用方法
│   ├── vendor                 # 公用vendor
│   ├── views                  # views 所有页面
│   ├── App.vue                # 入口页面
│   ├── main.js                # 入口文件 加载组件 初始化等
│   └── permission.js          # 权限管理
├── tests                      # 测试
├── .env.xxx                   # 环境变量配置
├── .eslintrc.js               # eslint 配置项
├── .babelrc                   # babel-loader 配置
├── .travis.yml                # 自动化CI配置
├── vue.config.js              # vue-cli 配置
├── postcss.config.js          # postcss 配置
└── package.json               # package.json

安装

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 克隆项目
git clone -b i18n https://github.com/PanJiaChen/vue-element-admin.git

# 进入项目目录
cd vue-element-admin

# 安装依赖
npm install

# 建议不要用 cnpm 安装 会有各种诡异的bug 可以通过如下操作解决 npm 下载速度慢的问题
npm install --registry=https://registry.npm.taobao.org

# 本地开发 启动项目
npm run dev

布局

页面整体布局是一个产品最外层的框架结构,往往会包含导航、侧边栏、面包屑以及内容等。想要了解一个后台项目,先要了解它的基础布局。

layout

对应代码

@/layout

@ 是 webpack 的 alias 别名

vue-element-admin 中大部分页面都是基于这个 layout 的,除了个别页面如:login , 404, 401 等页面没有使用该layout。如果你想在一个项目中有多种不同的layout也是很方便的,只要在一级路由那里选择不同的layout组件就行。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// No layout
{
  path: '/401',
  component: () => import('errorPage/401')
}

// Has layout
{
path: '/documentation',

// 你可以选择不同的layout组件
component: Layout,

// 这里开始对应的路由都会显示在app-main中 如上图所示
children: [{
path: 'index',
component: () => import('documentation/index'),
name: 'documentation'
}]
}

这里使用了 vue-router 路由嵌套, 所以一般情况下,你增加或者修改页面只会影响 app-main这个主体区域。

其它配置在 layout 中的内容如:侧边栏或者导航栏都是不会随着你主体页面变化而变化的。

app-main

对应代码

@/layout/components/AppMain

这里在 app-main 外部包了一层 keep-alive 主要是为了缓存 的,配合页面的 tabs-view 标签导航使用,如不需要可自行去除。

其中transition 定义了页面之间切换动画,可以根据自己的需求,自行修改转场动画。

默认提供了fadefade-transform两个转场动画,具体 css 实现见transition.scss。

如果需要调整可在AppMain.vue中调整transitionname

路由和侧边栏

路由和侧边栏是组织起一个后台应用的关键骨架。

本项目侧边栏和路由是绑定在一起的,所以你只有在 @/router/index.js 下面配置对应的路由,侧边栏就能动态的生成了。

项目配置路由配置项。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
// 当设置 true 的时候该路由不会在侧边栏出现 如401,login等页面,或者如一些编辑页面/edit/1
hidden: true // (默认 false)

//当设置 noRedirect 的时候该路由在面包屑导航中不可被点击
redirect: 'noRedirect'

// 当你一个路由下面的 children 声明的路由大于1个时,自动会变成嵌套的模式--如组件页面
// 只有一个时,会将那个子路由当做根路由显示在侧边栏--如引导页面
// 若你想不管路由下面的 children 声明的个数都显示你的根路由
// 你可以设置 alwaysShow: true,这样它就会忽略之前定义的规则,一直显示根路由
alwaysShow: true

name: 'router-name' // 设定路由的名字,一定要填写不然使用时会出现各种问题
meta: {
roles: ['admin', 'editor'] // 设置该路由进入的权限,支持多个权限叠加
title: 'title' // 设置该路由在侧边栏和面包屑中展示的名字
icon: 'svg-name' // 设置该路由的图标,支持 svg-class,也支持 el-icon-x element-ui 的 icon
noCache: true // 如果设置为true,则不会被 缓存(默认 false)
breadcrumb: false // 如果设置为false,则不会在breadcrumb面包屑中显示(默认 true)
affix: true // 如果设置为true,它则会固定在tags-view中(默认 false)

// 当路由设置了该属性,则会高亮相对应的侧边栏。
// 这在某些场景非常有用,比如:一个文章的列表页路由为:/article/list
// 点击文章进入文章详情页,这时候路由为/article/1,但你想在侧边栏高亮文章列表的路由,就可以进行如下设置
activeMenu: '/article/list'
}

示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
{
  path: '/permission',
  component: Layout,
  redirect: '/permission/index', //重定向地址,在面包屑中点击会重定向去的地址
  hidden: true, // 不在侧边栏显示
  alwaysShow: true, //一直显示根路由
  meta: { roles: ['admin','editor'] }, //你可以在根路由设置权限,这样它下面所有的子路由都继承了这个权限
  children: [{
    path: 'index',
    component: ()=>import('permission/index'),
    name: 'permission',
    meta: {
      title: 'permission',
      icon: 'lock', //图标
      roles: ['admin','editor'], //或者你可以给每一个子路由设置自己的权限
      noCache: true // 不会被  缓存
    }
  }]
}

路由

这里的路由分为两种,constantRoutesasyncRoutes

  1. constantRoutes: 代表那些不需要动态判断权限的路由,如登录页、404、等通用页面。
  2. asyncRoutes: 代表那些需求动态判断权限并通过 addRoutes 动态添加的页面。

路由层面权限的控制代码都在 @/permission.js 中,如果想修改逻辑,直接在适当的判断逻辑中 next() 释放钩子即可。

代码中封装了一个指令权限,能简单快速的实现按钮级别的权限判断。 v-permission

使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<template>
  
  <el-tag v-permission="['admin']">adminel-tag>


<el-tag v-permission="['editor']">editorel-tag>


<el-tag v-permission="['admin','editor']">Both admin or editor can see thisel-tag>
template>

<script>
// 当然你也可以为了方便使用,将它注册到全局
import permission from '@/directive/permission/index.js' // 权限判断指令
export default{
directives: { permission }
}
script>

可以使用全局权限判断函数,用法和指令 v-permission 类似。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<template>
  <el-tab-pane v-if="checkPermission(['admin'])" label="Admin">Admin can see thisel-tab-pane>
  <el-tab-pane v-if="checkPermission(['editor'])" label="Editor">Editor can see thisel-tab-pane>
  <el-tab-pane v-if="checkPermission(['admin','editor'])" label="Admin-OR-Editor">Both admin or editor can see thisel-tab-pane>
template>

<script>
import checkPermission from '@/utils/permission' // 权限判断函数

export default{
methods: {
checkPermission
}
}
script>

侧边栏

本项目侧边栏主要基于 element-uiel-menu 改造。

侧边栏是通过读取路由并结合权限判断而动态生成的,而且还需要支持路由无限嵌套,所以这里还使用到了递归组件。

代码地址@/layout/components/Sidebar

这里同时也改造了 element-ui 默认侧边栏不少的样式,所有的 css 都可以在 @/styles/sidebar.scss 中找到,你也可以根据自己的需求进行修改。

这里需要注意一下,一般侧边栏有两种形式即:submenu 和 直接 el-menu-item。 一个是嵌套子菜单,另一个则是直接一个链接。

Sidebar 中已经帮你做了判断,当你一个路由下面的 children 声明的路由大于>1 个时,自动会变成嵌套的模式。如果子路由正好等于一个就会默认将子路由作为根路由显示在侧边栏中,若不想这样,可以通过设置在根路由中设置alwaysShow: true来取消这一特性。如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
// No submenu, because children.length===1
{
  path: '/icon',
  component: Layout,
  children: [{
    path: 'index',
    component: ()=>import('svg-icons/index'),
    name: 'icons',
    meta: { title: 'icons', icon: 'icon' }
  }]
},

// Has submenu, because children.length>=1
{
path: '/components',
component: Layout,
name: 'component-demo',
meta: {
title: 'components',
icon: 'component'
},
children: [
{ path: 'tinymce', component: ()=>import('components-demo/tinymce'), name: 'tinymce-demo', meta: { title: 'tinymce' }},
{ path: 'markdown', component: ()=>import('components-demo/markdown'), name: 'markdown-demo', meta: { title: 'markdown' }},
]
}

多级目录(嵌套路由)

如果你的路由是多级目录,如本项目 @/views/nested 那样, 有三级路由嵌套的情况下,**不要忘记还要手动在二级目录的根文件下添加一个 **。

1
2
3
4
5
6
7
 
<template>
  <div>
    
    <router-view />
  div>
template>

如:@/views/nested/menu1/index.vue,原则上有多少级路由嵌套就需要多少个

面包屑

本项目中也封装了一个面包屑导航,它也是通过 watch $route 变化动态生成的。它和 menu 也一样,也可以通过之前那些配置项控制一些路由在面包屑中的展现。大家也可以结合自己的业务需求增改这些自定义属性。比如可以在路由中声明breadcrumb:false,让其不在 breadcrumb 面包屑显示。

代码地址

@/components/Breadcrumb

快捷导航(标签栏导航)

本功能是响应大家需求,后期加上的,其实本人在公司项目或者个人项目中是不太使用该功能的。以前那些传统后台框架往往会包含此功能,由于以前的后台项目大部分都是多页面的形式,所以标签栏导航功能还是具有一定意义的,大部分都是基于 iframe 的方式实现的。

但随着时代的发展,现在的后台项目几乎都是 spa(single page web application 单页面开发),再使用以前的方案来实现标签导航显然是不合适的。

所以目前的方案大致为: 运用 keep-aliverouter-view 的结合。

代码: @/layout/components/AppMain.vue

1
2
3
<keep-alive :include="cachedViews">
  <router-view>router-view>
keep-alive>

顶部标签栏导航实际作用相当于 nav 的另一种展现形式,其实说白了都是一个个 router-link,点击跳转到相应的页面。然后我们再来监听路由 $route 的变化,来判断当前页面是否需要重新加载或者已被缓存。

visitedViews && cachedViews

目前 tags-view 维护了两个数组。

  • visitedViews : 用户访问过的页面 就是标签栏导航显示的一个个 tag 数组集合
  • cachedViews : 实际 keep-alive 的路由。可以在配置路由的时候通过 meta.noCache 来设置是否需要缓存这个路由 默认都缓存。

样式

vue-element-admin 所有全局样式都在 @/styles 下设置

1
2
3
4
5
6
7
8
├── styles
│   ├── btn.scss                 # 按钮样式
│   ├── element-ui.scss          # 全局自定义 element-ui 样式
│   ├── index.scss               # 全局通用样式
│   ├── mixin.scss               # 全局mixin
│   ├── sidebar.scss             # sidebar css
│   ├── transition.scss          # vue transition 动画
│   └── variables.scss           # 全局变量

常见的工作流程是,全局样式都写在 src/styles 目录下,每个页面自己对应的样式都写在自己的 .vue 文件之中

1
2
3
4
5
6
7
<style>
/* global styles */
style>

<style scoped>
/* local styles */
style>

自定义 element-ui 样式

现在我们来说说怎么覆盖 element-ui 样式。由于 element-ui 的样式我们是在全局引入的,所以你想在某个页面里面覆盖它的样式就不能加 scoped,但你又想只覆盖这个页面的 element 样式,你就可在它的父级加一个 class,用命名空间来解决问题。

1
2
3
4
5
6
7
.article-page {
  /* 你的命名空间 */
  .el-tag {
    /* element-ui 元素*/
    margin-right: 0px;
  }
}

父组件改变子组件样式 深度选择器

当你子组件使用了 scoped 但在父组件又想修改子组件的样式可以 通过 >>> 来实现:

1
2
3
<style scoped>
.a >>> .b { /* ... */ }
style>

将会编译成

1
2
3
.a[data-v-f3f3eg9] .b {
  /* ... */
}

如果你使用了一些预处理的东西,如 sass 你可以通过 /deep/ 来代替 >>> 实现想要的效果。

所以你想覆盖某个特定页面 element 的 button 的样式,你可以这样做:

1
2
3
.xxx-container >>> .el-button{
  xxxx
}

异步交互

vue-element-admin 中,一个完整的前端 UI 交互到服务端处理流程是这样的:

  1. UI 组件交互操作;
  2. 调用统一管理的 api service 请求函数;
  3. 使用封装的 request.js 发送请求;
  4. 获取服务端返回;
  5. 更新 data;

从上面的流程可以看出,为了方便管理维护,统一的请求处理都放在 @/api 文件夹中,并且一般按照 model 纬度进行拆分文件,如:

1
2
3
4
5
api/
  login.js
  article.js
  remoteSearch.js
  ...

request.js

其中,@/utils/request.js 是基于 axios 的封装,便于统一处理 POST,GET 等请求参数,请求头,以及错误提示信息等。具体可以参看 request.js。 它封装了全局 request拦截器response拦截器统一的错误处理统一做了超时处理baseURL设置等

请求文章列表页的例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
// api/article.js
import request from '../utils/request';
export function fetchList(query) {
  return request({
    url: '/article/list',
    method: 'get',
    params: query
  })
}

// views/example/list
import { fetchList } from '@/api/article'
export default {
data() {
list: null,
listLoading: true
},
methods: {
fetchData() {
this.listLoading = true
fetchList().then(response => {
this.list = response.data.items
this.listLoading = false
})
}
}
}

设置多个 baseURL

我们可以通过环境变量设置多个baseURL,从而请求不同的 api 地址。

1
2
3
# .env.development
VUE_APP_BASE_API = '/dev-api' #注入本地 api 的根路径
VUE_APP_BASE_API2 = '/dev-api2' #注入本地 api2 的根路径

之后根据环境变量创建axios实例,让它具有不同的baseURL。 @/utils/request.js

1
2
3
4
5
6
7
8
9
10
// create an axios instance
const service = axios.create({
  baseURL: process.env.BASE_API, // api 的 base_url
  timeout: 5000 // request timeout
})

const service2 = axios.create({
baseURL: process.env.BASE_API2, // api2 的 base_url
timeout: 5000 // request timeout
})

或者

1
2
3
4
5
6
7
8
export function fetchList(query) {
  return request({
    url: '/article/list',
    method: 'get',
    params: query,
    baseURL: 'xxxx' // 直接通过覆盖的方式
  })
}

构建

当项目开发完毕,只需要运行一行命令就可以打包你的应用:

1
2
3
4
5
# 打包正式环境
npm run build:prod

# 打包预发布环境
npm run build:stage

构建打包成功之后,会在根目录生成 dist 文件夹,里面就是构建打包好的文件,通常是 ***.js***.cssindex.html 等静态文件。

如果需要自定义构建,比如指定 dist 目录等,则需要通过 config的 outputDir 进行配置。

ESLint

不管是多人合作还是个人项目,代码规范都是很重要的。这样做不仅可以很大程度地避免基本语法错误,也保证了代码的可读性。

所有的配置文件都在 .eslintrc.js 中。 本项目基本规范是依托于 vue 官方的 eslint 规则 eslint-config-vue 做了少许的修改。大家可以按照自己的需求进行定制化配置。

取消 ESLint 校验

如果你不想使用 ESLint 校验(不推荐取消),只要找到 vue.config.js 文件。 进行如下设置 lintOnSave: false 即可。

自动修复

npm run lint -- --fix

运行如上命令,eslint 会自动修复一些简单的错误。

国际化

本项目集合了国际化 i18n 方案。通过 vue-i18n而实现。

由于本项目 ui 框架使用了element,所以国际化的同时也要将其国际化。 完整代码。 同时将当前 lang 语言存在 cookie之中,为了下次打开页面能记住上次的语言设置。

全局 lang

代码地址: @/lang 目前配置了英文和中文两种语言。

同时在 @/lang/index.js 中引入了 element-ui的语言包

使用:

1
2
// $t 是 vue-i18n 提供的全局方法,更多信息请查看其文档
$t('login.title')

异步 lang

有一些某些特定页面才需要的 lang,比如 @/views/i18n-demo 页面

1
2
3
4
import local from './local'

this.$i18n.mergeLocaleMessage('en', local.en)
this.$i18n.mergeLocaleMessage('zh', local.zh)

js 中使用 $t

如果你使用如 select组件,它的值是通过 v-for而来,如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<el-select v-model="value">
  <el-option
    v-for="item in options"
    :key="item.value"
    :label="item.label"
    :value="item.value"/>
el-select>
this.options = [
  {
    value: '1',
    label: this.$t('i18nView.one')
  },
  {
    value: '2',
    label: this.$t('i18nView.two')
  },
  {
    value: '3',
    label: this.$t('i18nView.three')
  }
]

这种情况下,国际化只会执行一次,因为在 js 中的this.options只会在初始化的时候执行一次,它的数据并不会随着你本地 lang的变化而变化,所以需要你在lang变化的时候手动重设this.options

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
export default {
  watch: {
    lang() {
      this.setOptions()
    }
  },
  methods: {
    setOptions() {
      this.options = [
        {
          value: '1',
          label: this.$t('i18nView.one')
        },
        {
          value: '2',
          label: this.$t('i18nView.two')
        },
        {
          value: '3',
          label: this.$t('i18nView.three')
        }
      ]
    }
  }
}

移除国际化

src/main.js 中移除 import i18n from './lang' 并且删除 src/lang 文件夹。

并在 src/layout/components/Levelbarsrc/layout/components/SidebarItemsrc/layout/components/TabsView 等文件夹中 移除 this.$t('route.xxxx') 使用国际化的方式。

在v4.1.0+版本之后,默认 master 将不再提供国际化。因为大部分用户其实是用不到国际化的,但移除国际化工作量又相当的大。

作业

熟悉 vue-element-admin 框架的使用

    

相关