vue-element-admin

vue-element-admin

介绍

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

这里是官网地址

这里是线上demo地址

如果你想查看该项目的具体功能和效果,可以拉取代码,启动进行预览¥

$ git clone https://github.com/PanJiaChen/vue-element-admin.git  #拉取代码
$ cd vue-element-admin #切换到具体目录下
$ npm run dev  #启动开发调试模式  查看package.json文件的scripts可知晓启动命令

建议

本项目的定位是后台集成方案,不太适合当基础模板来进行二次开发。因为本项目集成了很多你可能用不到的功能,会造成不少的代码冗余。如果你的项目不关注这方面的问题,也可以直接基于它进行二次开发。

功能

- 登录 / 注销

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

- 多环境发布
  - 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

前序准备

你需要在本地安装 nodegit。本项目技术栈基于 ES2015+vuevuexvue-routervue-cliaxioselement-ui,所有的请求数据都使用Mock.js进行模拟,提前了解和学习这些知识会对使用本项目有很大的帮助。

同时配套一个系列的教程文章,如何从零构建一个完整的管理后台项目,建议大家先看完这些文章再来实践本项目。

本项目不支持低版本浏览器(如 ie),有需求请自行添加 polyfill 详情

目录结构

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

├── 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

此时,你可能会眼花缭乱, 因为生成的目录里面有太多的文件 我们在做项目时 其中最关注的就是**src**目录, 里面是所有的源代码和资源, 至于其他目录, 都是对项目的环境和工具的配置

项目运行机制和代码注释

为了让我们开发简单明了,我们只需要了解对项目比较重要的几个目录

├── src                        # 源代码
│   ├── api                    # 所有请求
│   ├── assets                 # 主题 字体等静态资源
│   ├── components             # 全局公用组件
│   ├── icons                  # 项目所有 svg icons
│   ├── layout                 # 全局 layout
│   ├── router                 # 路由
│   ├── store                  # 全局 store管理
│   ├── styles                 # 全局样式
│   ├── utils                  # 全局公用方法
│   ├── vendor                 # 公用vendor
│   ├── views                  # views 所有页面
│   ├── App.vue                # 入口页面
│   ├── main.js                # 入口文件 加载组件 初始化等
│   └── permission.js          # 权限管理
│   └── settings.js            # 配置文件

main.js

image-20200824153141764

请注释掉**mock数据的部分,删除src下的mock**文件夹,我们开发的时候用不到模拟数据,如图

同时,请注释掉**vue.config.js*中的 *before: require(’./mock/mock-server.js’)

image-20200811013813693

App.vue

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-n1teUB1k-1621567701026)(http://blog.daqitc.net/vue/vue-admin-tempate/assets/image-20200824155103340.png)]

permission.js

src下,除了main.js还有两个文件,permission.js 和**settings.js**

permission.js 是控制页面登录权限的文件, 此处的代码没有经历构建过程会很难理解, 所以先将此处的代码进行注释,等我们构建权限功能时,再从0到1进行构建。

注释代码

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-uwBCH4IC-1621567701028)(http://blog.daqitc.net/vue/vue-admin-tempate/assets/image-20200708014558617.png)]

settings.js*则是对于一些项目信息的配置,里面有三个属性 *title(项目名称),fixedHeader(固定头部),sidebarLogo(显示左侧菜单logo)

**settings.js**中的文件在其他的位置会引用到,所以这里暂时不去对该文件进行变动

Vuex结构

当前的Vuex结构采用了模块形式进行管理共享状态,其架构如下

image-20200824165153331

其中app.js模块和settings.js模块,功能已经完备,不需要再进行修改。 user.js模块是我们后期需要重点开发的内容,所以这里我们将user.js里面的内容删除,并且导出一个默认配置

export default  {
  namespaced: true,
  state: {},
  mutations: {},
  actions: {}
}

同时,由于getters中引用了user中的状态,所以我们将getters中的状态改为

const getters = {
  sidebar: state => state.app.sidebar,
  device: state => state.app.device
}
export default getters

scss

该项目还使用了scss作为css的扩展语言,在**styles**目录下,我们可以发现scss的相关文件,相关用法 我们下一小节 进行讲解[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Qkzviryp-1621567701029)(http://blog.daqitc.net/vue/vue-admin-tempate/assets/image-20200824171327384.png)]

icons

icons的结构如下

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-VKocld3A-1621567701029)(http://blog.daqitc.net/vue/vue-admin-tempate/assets/image-20200824173239955.png)]

以上就是vue-element-admin的基础和介绍,希望大家在这过程中体会 一个基础的模板运行机制

安装

# 克隆项目
git clone 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

TIP

强烈建议不要用直接使用 cnpm 安装,会有各种诡异的 bug,可以通过重新指定 registry 来解决 npm 安装速度慢的问题。若还是不行,可使用 yarn 替代 npm

Windows 用户若安装不成功,很大概率是node-sass安装失败,解决方案

另外因为 node-sass 是依赖 python环境的,如果你之前没有安装和配置过的话,需要自行查看一下相关安装教程。

启动完成后会自动打开浏览器访问 http://localhost:9527, 你看到下面的页面就代表操作成功了。

img

接下来你可以修改代码进行业务开发了,本项目内建了典型业务模板、常用业务组件、模拟数据、HMR 实时预览、状态管理、国际化、全局路由等等各种实用的功能来辅助开发,你可以继续阅读和探索左侧的其它文档。

集成方案并不适合我们直接拿来进行二次开发,基础模板则是一个更好的选择

基础模板, 包含了基本的 登录 / 鉴权 / 主页布局 的一些基础功能模板, 我们可以直接在该模板上进行功能的扩展和项目的二次开发

建议

你可以把 vue-element-admin当做工具箱或者集成方案仓库,在 vue-admin-template 的基础上进行二次开发,想要什么功能或者组件就去 vue-element-admin 那里复制过来。

Contribution

本文档项目地址 vue-element-admin-site 基于 vuepress开发。

有任何修改和建议都可以该项目 pr 和 issue

vue-element-admin 还在持续迭代中,逐步沉淀和总结出更多功能和相应的实现代码,总结中后台产品模板/组件/业务场景的最佳实践。

Vue 生态圈

首先了解这些 vue 生态圈的东西,会对你上手本项目有很大的帮助。

  1. Vue Router 是 vue 官方的路由。它能快速的帮助你构建一个单页面或者多页面的项目。

  2. Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式。它采用集中式存储管理应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化。它能解决你很多全局状态或者组件之间通信的问题。

  3. Vue Loader 是为 vue 文件定制的一个 webpack 的 loader,它允许你以一种名为单文件组件 (SFCs)的格式撰写 Vue 组件。它能在开发过程中使用热重载来保持状态,为每个组件模拟出 scoped CSS 等等功能。不过大部分情况下你不需要对它直接进行配置,脚手架都帮你封装好了。

  4. Vue Test Utils 是官方提供的一个单元测试工具。它能让你更方便的写单元测试。

  5. Vue Dev-Tools Vue 在浏览器下的调试工具。写 vue 必备的一个浏览器插件,能大大的提高你调试的效率。

  6. Vue CLI 是官方提供的一个 vue 项目脚手架,本项目也是基于它进行构建的。它帮你封装了大量的 webpack、babel 等其它配置,让你能花更少的精力在搭建环境上,从而能更专注于页面代码的编写。不过所有的脚手架都是针对大部分情况的,所以一些特殊的需求还是需要自己进行配置。建议先阅读一遍它的文档,对一些配置有一些基本的了解。

  7. Vetur 是 VS Code 的插件. 如果你使用 VS Code 来写 vue 的话,这个插件是必不可少的。

Vue 全家桶

Vue有著名的全家桶系列,包含了

概括起来就是:

  1. 项目构建工具(vue-cli)
  • vue-cli也叫脚手架,官方定义为Vue.js 开发的标准工具!相比scirpt标签引入,脚手架具有如下特点:1)、功能丰富

    • 对 Babel、TypeScript、ESLint、PostCSS、PWA、单元测试和 End-to-end 测试提供开箱即用的支持。

    2)、易于扩展

    • 它的插件系统可以让社区根据常见需求构建和共享可复用的解决方案。

    3)、无需 Eject

    • Vue CLI 完全是可配置的,无需 eject。这样你的项目就可以长期保持更新了。

    4)、CLI 之上的图形化界面

    • 通过配套的图形化界面创建、开发和管理你的项目。

    5)、即刻创建原型

    • 用单个 Vue 文件即刻实践新的灵感。

    6)、面向未来

    • 为现代浏览器轻松产出原生的 ES2015 代码,或将你的 Vue 组件构建为原生的 Web Components 组件。

    安装

    # npm install -g @vue/cli
    
    # OR
    yarn global add @vue/cli
    
    # 安装完成后创建一个项目,vue ui为图形化构建,相对简单(推荐)
    vue create my-project
    
    # OR
    vue ui
    
  1. 路由(vue-router)
  • Vue Router 是 Vue.js 官方的路由管理器。它和 Vue.js 的核心深度集成,让构建单页面应用变得易如反掌。包含的功能有:

    1)嵌套的路由/视图表

    2)模块化的、基于组件的路由配置

    3)路由参数、查询、通配符

    4)基于 Vue.js 过渡系统的视图过渡效果

    5)细粒度的导航控制

    6)带有自动激活的 CSS class 的链接

    7)HTML5 历史模式或 hash 模式,在 IE9 中自动降级

    8)自定义的滚动条行为

  • 安装

 ```js
 npm install vue-router
 
 //安装后在mainjs引入
 import VueRouter from 'vue-router'
 
 Vue.use(VueRouter)
 ```
  1. 状态管理(vuex)
  • Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式。它采用集中式存储管理应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化。Vuex 也集成到 Vue 的官方调试工具 devtools extension,提供了诸如零配置的 time-travel 调试、状态快照导入导出等高级调试功能。

  • 什么情况下我应该使用 Vuex?

    • Vuex 可以帮助我们管理共享状态,并附带了更多的概念和框架。这需要对短期和长期效益进行权衡。
    • 如果您不打算开发大型单页应用,使用 Vuex 可能是繁琐冗余的。确实是如此——如果您的应用够简单,您最好不要使用 Vuex。一个简单的 store 模式就足够您所需了。但是,如果您需要构建一个中大型单页应用,您很可能会考虑如何更好地在组件外部管理状态,Vuex 将会成为自然而然的选择。

    安装方法

    npm install vuex --save
    
  1. http请求工具(axios)
  • axios是一个http请求包,vue官网推荐使用axios进行http调用。

  • Axios 是一个基于 promise 的 HTTP 库,可以用在浏览器和 node.js 中。

  • 特性

    1)从浏览器中创建 XMLHttpRequests

    2)从 node.js 创建 http 请求

    3)支持 Promise API

    4)拦截请求和响应

    5)转换请求数据和响应数据

    6)取消请求

    7)自动转换 JSON 数据

    8)客户端支持防御 XSRF

  • 安装

 ```
 npm install axios
 ```
  1. 有的人也会把 ui组件(如iview、vant、elementUI等等),当作全家桶的一员

    • iview 一套基于 Vue的高质量UI 组件库(分为小程序和pc端等不同版本);
    • vant 轻量、可靠的移动端 Vue 组件库,是有赞开源的一套基于 Vue 2.0 的 Mobile 组件库,旨在更快、更简单地开发基于 Vue 的美观易用的移动站点。
    • Ant Design Vue 是 Ant Design 的 Vue 实现,开发和服务于企业级后台产品。
    • elementUI 是基于 Vue 2.0 桌面端中后台组件库。

Vue两大核心思想:组件化和数据驱动。

  • 组件化:把整体拆分为各个可以复用的个体,
  • 数据驱动:通过数据变化直接影响bom展示,避免dom操作

vuex基础

vuex基础-介绍

为什么会有Vuex ?

Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式。它采用**集中式存储管理应用的所有组件的状态,并以相应的规则保证状态以一种可预测**的方式发生变化。

  • vuex是采用集中式管理组件依赖的共享数据的一个工具,可以解决不同组件数据共享问题。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Nx8RDAsd-1621567701031)(http://blog.daqitc.net/vue/vue-admin-tempate/assets/image-20200902235150562.png)]

结论

  1. 修改state状态必须通过**mutations**
  2. **mutations**只能执行同步代码,类似ajax,定时器之类的代码不能在mutations中执行
  3. 执行异步代码,要通过actions,然后将数据提交给mutations才可以完成
  4. state的状态即共享数据可以在组件中引用
  5. 组件中可以调用action
vuex有哪几种属性?

有五种,分别是 State、 Getter、Mutation 、Action、 Module

  • state => 基本数据(数据源存放地)
  • getters => 从基本数据派生出来的数据
  • mutations => 提交更改数据的方法,同步!
  • actions => 像一个装饰器,包裹mutations,使之可以异步。
  • modules => 模块化Vue

vuex基础-初始化功能

建立一个新的脚手架项目, 在项目中应用vuex

$ vue create  demo

开始vuex的初始化建立,选择模式时,选择默认模式

初始化:

  • 第一步:npm i vuex --save => 安装到**运行时依赖** => 项目上线之后依然使用的依赖 ,开发时依赖 => 开发调试时使用

开发时依赖 就是开开发的时候,需要的依赖,运行时依赖,项目上线运行时依然需要的

  • 第二步: 在main.js中 import Vuex from 'vuex'
  • 第三步:在main.js中 Vue.use(Vuex) => 调用了 vuex中的 一个install方法
  • 第四步:const store = new Vuex.Store({...配置项})
  • 第五步:在根实例配置 store 选项指向 store 实例对象
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(vuex)
const store = new Vuex.Store({})
new Vue({
  el: '#app',
  store
})

vuex基础-state

state是放置所有公共状态的属性,如果你有一个公共状态数据 , 你只需要定义在 state对象中

定义state

// 初始化vuex对象
const store = new Vuex.Store({
  state: {
    // 管理数据
    count: 0
  }
})

如何在组件中获取count?

原始形式- 插值表达式

App.vue

组件中可以使用 this.$store 获取到vuex中的store对象实例,可通过state属性属性获取count, 如下

<div> state的数据:{{ $store.state.count }}</div>

计算属性 - 将state属性定义在计算属性中

// 把state中数据,定义在组件内的计算属性中
  computed: {
    count () {
      return this.$store.state.count
    }
  }
 <div> state的数据:{{ count }}</div>

辅助函数 - mapState

mapState是辅助函数,帮助我们把store中的数据映射到 组件的计算属性中, 它属于一种方便用法

用法 : 第一步:导入mapState

import { mapState } from 'vuex'

第二步:采用数组形式引入state属性

mapState(['count']) 

上面代码的最终得到的是 类似

count () {
    return this.$store.state.count
}

第三步:利用延展运算符将导出的状态映射给计算属性

  computed: {
    ...mapState(['count'])
  }
 <div> state的数据:{{ count }}</div>

vuex基础-mutations

state数据的修改只能通过mutations,并且mutations必须是同步更新,目的是形成**数据快照**

数据快照:一次mutation的执行,立刻得到一种视图状态,因为是立刻,所以必须是同步

定义mutations

const store  = new Vuex.Store({
  state: {
    count: 0
  },
  // 定义mutations
  mutations: {
     
  }
})

格式说明

mutations是一个对象,对象中存放修改state的方法

mutations: {
    // 方法里参数 第一个参数是当前store的state属性
    // payload 载荷 运输参数 调用mutaiions的时候 可以传递参数 传递载荷
    addCount (state) {
      state.count += 1
    }
  },

如何在组件中调用mutations

原始形式-$store

新建组件child-a.vue,内容为一个button按钮,点击按钮调用mutations

<template>
  <button @click="addCount">+1</button>
</template>

<script>
export default {
    methods: {
    //   调用方法
      addCount () {
         // 调用store中的mutations 提交给muations
        // commit('muations名称', 2)
        this.$store.commit('addCount', 10)  // 直接调用mutations
    }
  }
}
</script>

带参数的传递

    addCount (state, payload) {
        state.count += payload
    }
    this.$store.commit('addCount', 10)

辅助函数 - mapMutations

mapMutations和mapState很像,它把位于mutations中的方法提取了出来,我们可以将它导入

import  { mapMutations } from 'vuex'
methods: {
    ...mapMutations(['addCount'])
}

上面代码的含义是将mutations的方法导入了methods中,等同于

methods: {
      // commit(方法名, 载荷参数)
      addCount () {
          this.$store.commit('addCount')
      }
 }

此时,就可以直接通过this.addCount调用了

<button @click="addCount(100)">+100</button>

但是请注意: Vuex中mutations中要求不能写异步代码,如果有异步的ajax请求,应该放置在actions中

vuex基础-actions

state是存放数据的,mutations是同步更新数据,actions则负责进行异步操作

定义actions

 actions: {
  //  获取异步的数据 context表示当前的store的实例 可以通过 context.state 获取状态 也可以通过context.commit 来提交mutations, 也可以 context.diapatch调用其他的action
    getAsyncCount (context) {
      setTimeout(function(){
        // 一秒钟之后 要给一个数 去修改state
        context.commit('addCount', 123)
      }, 1000)
    }
 } 

原始调用 - $store

 addAsyncCount () {
     this.$store.dispatch('getAsyncCount')
 }

传参调用

 addAsyncCount () {
     this.$store.dispatch('getAsyncCount', 123)
 }

辅助函数 -mapActions

actions也有辅助函数,可以将action导入到组件中

import { mapActions } from 'vuex'
methods: {
    ...mapActions(['getAsyncCount'])
}

直接通过 this.方法就可以调用

<button @click="getAsyncCount(111)">+异步</button>

vuex基础-getters

除了state之外,有时我们还需要从state中派生出一些状态,这些状态是依赖state的,此时会用到getters

例如,state中定义了list,为1-10的数组,

state: {
    list: [1,2,3,4,5,6,7,8,9,10]
}

组件中,需要显示所有大于5的数据,正常的方式,是需要list在组件中进行再一步的处理,但是getters可以帮助我们实现它

定义getters

  getters: {
    // getters函数的第一个参数是 state
    // 必须要有返回值
     filterList:  state =>  state.list.filter(item => item > 5)
  }

使用getters

原始方式 -$store

<div>{{ $store.getters.filterList }}</div>

辅助函数 - mapGetters

computed: {
    ...mapGetters(['filterList'])
}
 <div>{{ filterList }}</div>

Vuex中的模块化-Module

为什么会有模块化?

由于使用单一状态树,应用的所有状态会集中到一个比较大的对象。当应用变得非常复杂时,store 对象就有可能变得相当臃肿。

这句话的意思是,如果把所有的状态都放在state中,当项目变得越来越大的时候,Vuex会变得越来越难以维护

由此,又有了Vuex的模块化

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-jbQc900T-1621567701031)(http://blog.daqitc.net/vue/vue-admin-tempate/assets/image-20200904155846709.png)]

模块化的简单应用

应用

定义两个模块 usersetting

user中管理用户的状态 token

setting中管理 应用的名称 name

const store  = new Vuex.Store({
  modules: {
    user: {
       state: {
         token: '12345'
       }
    },
    setting: {
      state: {
         name: 'Vuex实例'
      }
    }
  })

定义child-b组件,分别显示用户的token和应用名称name

<template>
  <div>
      <div>用户token {{ $store.state.user.token }}</div>
      <div>网站名称 {{ $store.state.setting.name }}</div>
  </div>
</template>

请注意: 此时要获取子模块的状态 需要通过 $store.state.模块名称.属性名 来获取

看着获取有点麻烦,我们可以通过之前学过的getters来改变一下

 getters: {
   token: state => state.user.token,
   name: state => state.setting.name
 } 

请注意:这个getters是根级别的getters哦

通过mapGetters引用

 computed: {
       ...mapGetters(['token', 'name'])
 }
模块化中的命名空间

命名空间 namespaced

这里注意理解

默认情况下,模块内部的 action、mutation 和 getter 是注册在全局命名空间的——这样使得多个模块能够对同一 mutation 或 action 作出响应。

这句话的意思是 刚才的user模块还是setting模块,它的 action、mutation 和 getter 其实并没有区分,都可以直接通过全局的方式调用 如

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-KsQnTXdL-1621567701032)(http://blog.daqitc.net/vue/vue-admin-tempate/assets/image-20200904164007116.png)]

  user: {
       state: {
         token: '12345'
       },
       mutations: {
        //  这里的state表示的是user的state
         updateToken (state) {
            state.token = 678910
         }
       }
    },

通过mapMutations调用

 methods: {
       ...mapMutations(['updateToken'])
  }
 <button @click="updateToken">修改token</button>

但是,如果我们想保证内部模块的高封闭性,我们可以采用namespaced来进行设置

高封闭性?可以理解成 一家人如果分家了,此时,你的爸妈可以随意的进出分给你的小家,你觉得自己没什么隐私了,我们可以给自己的房门加一道锁(命名空间 namespaced),你的父母再也不能进出你的小家了

  user: {
       namespaced: true,
       state: {
         token: '12345'
       },
       mutations: {
        //  这里的state表示的是user的state
         updateToken (state) {
            state.token = 678910
         }
       }
    },

使用带命名空间的模块 action/mutations

方案1:直接调用-带上模块的属性名路径

test () {
   this.$store.dispatch('user/updateToken') // 直接调用方法
}

方案2:辅助函数-带上模块的属性名路径

  methods: {
       ...mapMutations(['user/updateToken']),
       test () {
           this['user/updateToken']()
       }
   }
  <button @click="test">修改token</button>

方案3: createNamespacedHelpers 创建基于某个命名空间辅助函数

import { mapGetters, createNamespacedHelpers } from 'vuex'
const { mapMutations } = createNamespacedHelpers('user')
<button @click="updateToken">修改token2</button>

关于Vuex的更多用法,后续在项目中讲解

SCSS

SCSS处理的了解和使用

目标: 了解和学习Scss处理器的规范和用法

官方文档

首先注意,这里的sass和我们的scss是什么关系

sass和scss其实是**一样的**css预处理语言,SCSS 是 Sass 3 引入新的语法,其后缀名是分别为 .sass和.scss两种。 SASS版本3.0之前的后缀名为.sass,而版本3.0之后的后缀名.scss。 两者是有不同的,继sass之后scss的编写规范基本和css一致,sass时代是有严格的缩进规范并且没有‘{}’和‘;’。 而scss则和css的规范是一致的。

搭建小型测试环境

为了方便应用scss,我们可以在vscode中安装一个名为**easy sass** 的插件,但是我们只在该项目中工作区中应用该插件,因为在项目中,不需要该插件的辅助

image-20200825143713888

首先我们新建一个文件夹test,然后我们在test下新建一个index.html,并新建一个test.scss

页面结构如下

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <link rel="stylesheet" href="./test.css">
</head>
<body>
    <nav> Scss样式 </nav>
    <div id="app">
        Hello World
    </div>
    <div id="content">
        <article>
            <h1>文章标题</h1>
            <p>文章内容 <a href="">百度</a> </p>
        </article>
        <aside>
            侧边栏
        </aside>
    </div>
</body>
</html>

我们使用的**easy sass插件会自动的帮助我们把test.scss => test.css**

变量

sass使用$符号来标识变量

$highlight-color: #f90     

上面我们声明了一个 名为**$highlight-color**的变量, 我们可以把该变量用在任何位置

#app {
    background-color:  $highlight-color;
}     

以空格分割的多属性值也可以标识变量

$basic-border: 1px solid black;
#app {
    background-color:  $highlight-color;
    border: $basic-border
}     

变量范围

CSS属性不同,变量可以在css规则块定义之外存在。当变量定义在css规则块内,那么该变量只能在此规则块内使用。如果它们出现在任何形式的{...}块中(如@media或者@font-face块),情况也是如此:

$nav-color: #F90;
nav {
  $width: 100px;
  width: $width;
  color: $nav-color;
  background-color: black
}

# 编译后 

nav {
  width: 100px;
  color: #F90;
  background-color: black;
}

在这段代码中,$nav-color这个变量定义在了规则块外边,所以在这个样式表中都可以像 nav规则块那样引用它。$width这个变量定义在了nav{ }规则块内,所以它只能在nav规则块 内使用。这意味着是你可以在样式表的其他地方定义和使用$width变量,不会对这里造成影响。

嵌套语法

和less一样,scss同样支持**嵌套型**的语法

#content {
    article {
      h1 { color: #1dc08a }
      p {  font-style: italic; }
    }
    aside { background-color: #f90 }
  }

转化后

#content article h1 {
  color: #1dc08a;
}

#content article p {
  font-style: italic;
}

#content aside {
  background-color: #f90;
}

&父选择器

假如你想针对某个特定子元素 进行设置

比如

  #content {
    article {
      h1 { color: #1dc08a }
      p {  font-style: italic; }
      a {
        color: blue;
        &:hover { color: red }
      }
    }
    aside { background-color: #f90 }
  }

学到这里,我们会发现scss和less有很多相似之处,最大的区别就在于声明变量的方式,less采用的是**@变量名, 而scss采用的$变量名**

此时,我们再来看一下模板中的 styles/variables.scss

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-8Y3ZpGB2-1621567701033)(http://blog.daqitc.net/vue/vue-admin-tempate/assets/image-20201017230542301.png)]

上述文件,实际上定义了我们的一些基础数值,方便大家在某个文件统一的处理

模块化和组件化

什么是组件和模块化

组件化

组件的出现,就是为了拆分Vue实例的代码量的,能够让我们以不同的组件,来划分不同的功能模块,将来我们需要什么样的功能,就可以去调用对应的组件即可;

模块化

我们说Vue项目是按照模块进行划分的,那么什么是模块呢?

模块化,就相当于我们网站的导航栏,那么每一个导航项就可以看作是如下图片中提到的

image.png

模块化,就好比是一个大的功能项,那么这个大的功能项又可以包含多个组件。

在使用的时候,单个模块对应的是我们Vue项目下compentent下的文件目录

每一个模块下面又可以有多个组件,这些组件又组成了完整的模块页面(单页面)

但是每一个模块下面必须要有一个主要的文件(模块父组件),这个文件必须在路由管理中(router/index.js)进行注册,注册的时候满足以下形式:

export default new Router({
  mode: 'history',
  routes: [
		{
      path: '/',
      name: 'Default',
      redirect: '/home',
      component: Home
    }]
    })

其余的组件可以通过以下的方式进行注入使用。在使用的时候,将组件的name属性中的名称在父组件中进行标签化处理(vue会将以驼峰命名的名称进行转化--------(BookManager——>book-manager))

  • 首先要在用到的页面中的中通过import进行导入
  • 其次需要在compent中进行注册,注册的时候会使用组件的name属性进行注入

组件化和模块化的不同

  • 模块化: 是从代码逻辑的角度进行划分的;方便代码分层开发,保证每个功能模块的职能单一;
  • 组件化: 是从UI界面的角度进行划分的;前端的组件化,方便UI组件的重用;

全局组件定义的三种方式

先叨叨一下全局组件,首先,组件定义在script标签里面,其次可以不单单#app使用,作为全局组件,任意写一个实例都可以调用改全局组件。

1、使用Vue.extend配合Vue.component方法:
//1.1使用Vue.extend来创建全局Vue组件
var coml = Vue.extend({
    template:"<h3>这是使用Vue.extend创建的组件</h3>"//通过templa属性,指定了组件要展示的HTML结构
})

//1.2使用Vue.component("组建的名称",创建出来的组件模板对象)
Vue.componet("myCom1",com1)
//如果要使用组件,直接把组件名称,以HTML标签的形式,引入到页面当中即可

<my-coml></my-coml>

//如果使用驼峰定义全局组建的时候,啧啧在引用的时候将大写驼峰改成小写,同时两个单词之间使用-连接

//如果不适用驼峰,直接拿名称使用即可
2、将对象值直接传入template。

Vue.component 第一个参数组件的名称。将来在引用组件的时候,就是一个标签 Vue.extend。第二个参数:Vue.extend 创建的组件,其中template就是组件要展示的内容。

Vue.component("mycoml",Vue.extend{
template:"
<div>
<h3>这是使用Vue.extend创建的组件</h3>"
<span>123</span>
</div>
})

不论是那种方式创建出来的组件,组件的template属性指向的模板内容,必须有且只有一个根元素。

3、将template直接抽离出去,在外面设置结构。
//再被控制的#app外面,使用template元素,定义组件的HTML模板结构
<template id ="tmpl">
    <div>
    <h1>这是通过template元素,在外部定义的组件结构,有代码的智能提示</h1>
    </div>
</template>
<script>
    Vue.component("mycom3",{
        template:"#tmp1"
    })
</script>

定义一个私有组件:

var vm2 = new Vue({
    el:"#app",
    data:{},
    methods{},
    filters{},
    components{//定义实例内部私有组件的,私有组件只能在vm2内调用
    login:{
        template:"<h1>这是私有的login组件</h1>"
    },     
})

同样,也可以将 <h1></h1> 抽离出

//抽离出的template部分

<template id="tmpl">
    <h1>这是私有的login组件</h1>
</template>


components:{//vm2中的部分
    login:{
        template:"#tnp1"
    }
}

组件化的时候什么时候写成文件夹形式

如果组件里面业务逻辑较多,并且有一个自己用的嵌套组件,这个时候就需要写成文件夹形式,反之则可以直接携程文件进行调用即可

封装 vue 组件的过程

  1. 建立组件的模板,先把架子搭起来,写写样式,考虑好组件的基本逻辑。

2. 准备好组件的数据输入。即分析好逻辑,定好 props 里面的数据、类型。
  3. 准备好组件的数据输出。即根据组件逻辑,做好要暴露出来的方法。
  4. 封装完毕了,直接调用即可

建立远程Git仓库并完成初始提交

码云或者github上建立相应的远程仓库,并将代码分支提交

建立远程仓库

远程仓库建立只需要在网站上直接操作即可

本地项目提交

注意: 由于我们之前的项目是直接从 vue-element-admin **克隆\ ** 而来,里面拥有原来的提交记录,为了避免冲突, 先将原来的 .git 文件夹删除掉

并且对项目进行git初始化

$ git init  #初始化项目
$ git add . #将修改添加到暂存
$ git commit -m '人资项目初始化' #将暂存提到本地仓库

查看版本日志

$ git log #查看版本日志

推送到远程仓库

推送到远程仓库一般先将**远程仓库地址**用本地仓库别名代替

$ git remote add origin <远程仓库地址>  #添加远程仓库地址

当我们不清楚自己的仓库对应的origin地址时, 我们可以通过命令查看当前的远程仓库地址

$ git remote -v #查看本地仓库的远程仓库地址映射

推送master分支到远程仓库

$ git push -u origin master #将master分支推送到origin所代表的远程仓库地址

API模块和请求封装模块介绍

Axios的拦截器介绍

该项目采用了API的单独模块封装和axios拦截器的方式进行开发

axios的拦截器原理如下

image-20200811012945409

axios拦截器

axios作为网络请求的第三方工具, 可以进行请求和响应的拦截

通过create创建了一个新的axios实例

// 创建了一个新的axios实例
const service = axios.create({
  baseURL: process.env.VUE_APP_BASE_API, // url = base url + request url
  // withCredentials: true, // send cookies when cross-domain requests
  timeout: 5000 // 超时时间
})

请求拦截器

请求拦截器主要处理 token的**统一注入问题**

// axios的请求拦截器
service.interceptors.request.use(
  config => {
    // do something before request is sent

    if (store.getters.token) {
      // let each request carry token
      // ['X-Token'] is a custom headers key
      // please modify it according to the actual situation
      config.headers['X-Token'] = getToken()
    }
    return config
  },
  error => {
    // do something with request error
    console.log(error) // for debug
    return Promise.reject(error)
  }
)

响应拦截器

响应拦截器主要处理 返回的**数据异常** 和**数据结构**问题

// 响应拦截器
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
      })
      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)
  }
)

这里为了后续更清楚的书写代码,我们将原有代码注释掉,换成如下代码

// 导出一个axios的实例  而且这个实例要有请求拦截器 响应拦截器
import axios from 'axios'
const service = axios.create() // 创建一个axios的实例
service.interceptors.request.use() // 请求拦截器
service.interceptors.response.use() // 响应拦截器
export default service // 导出axios实例

api模块的单独封装

我们习惯性的将所有的网络请求 放置在api目录下统一管理,按照模块进行划分

单独封装代码

import request from '@/utils/request'

export function login(data) {
  return request({
    url: '/vue-admin-template/user/login',
    method: 'post',
    data
  })
}

export function getInfo(token) {
  return request({
    url: '/vue-admin-template/user/info',
    method: 'get',
    params: { token }
  })
}

export function logout() {
  return request({
    url: '/vue-admin-template/user/logout',
    method: 'post'
  })
}

上面代码中,使用了封装的request工具,每个接口的请求都单独**导出**了一个方法,这样做的好处就是,任何位置需要请求的话,可以直接引用我们导出的请求方法

为了后续更好的开发,我们可以先将user.js代码的方法设置为空,后续在进行更正

// import request from '@/utils/request'

export function login(data) {

}

export function getInfo(token) {

}

export function logout() {

}

项目开发

文档:http://blog.daqitc.net/#/vue/vue-admin-tempate/01-vuex%E5%8A%A0%E5%BC%BA

登录模块

设置固定的本地访问端口和网站名称

目标: 设置统一的本地访问端口和网站title

在正式开发业务之前,先将项目的本地端口网站名称进行一下调整

本地服务端口: 在**vue.config.js**中进行设置

vue.config.js 就是vue项目相关的编译,配置,打包,启动服务相关的配置文件,它的核心在于webpack,但是又不同于webpack,相当于改良版的webpack, 文档地址

如图,是开发环境服务端口的位置

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-jbBTIThn-1621567701035)(http://blog.daqitc.net/vue/vue-admin-tempate/assets/image-20200710162221402.png)]

我们看到上面的 **process.env.port**实际上是一个nodejs服务下的环境变量,该变量在哪里设置呢?

在项目下, 我们发现了**.env.development.env.production**两个文件

development => 开发环境

production => 生产环境

当我们运行npm run dev进行开发调试的时候,此时会加载执行**.env.development**文件内容

当我们运行npm run build:prod进行生产环境打包的时候,会加载执行**.env.production**文件内容

所以,如果想要设置开发环境的接口,直接在**.env.development**中写入对于port变量的赋值即可

# 设置端口号
port = 8888

本节注意:修改服务的配置文件,想要生效的话,必须要重新启动服务,值‘8888’后面不能留有空格

网站名称

网站名称实际在configureWebpack选项中的name选项,通过阅读代码,我们会发现name实际上来源于src目录下

**settings.js**文件

所以,我们可以将网站名称改成"人力资源管理平台"

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-VecPfVxN-1621567701035)(http://blog.daqitc.net/vue/vue-admin-tempate/assets/image-20210503140549664.png)]

登录表单的校验

对登录表单进行规则校验

基础模板已经有了基础校验的代码,所以我们这一章节更多的是修正和完善

el-form表单校验的先决条件

image-20200830212537835

用户名和密码的校验

字段名对应

为什么要对应? 因为基础模板采用的是**username的字段,但是实际接口中采用的是mobile的字段,为了更方便的写代码,所以我们将username改成mobile**

这里除了字段名,还有我们的规则校验名称,以及prop名称。

英文提示变成中文

基础模板中都是placeHolder占位符是英文,要变成中文

登录按钮文字同样需要换成中文

校验用户名和校验密码

基础模板中,已经做了校验,我们针对代码进行一些优化

 loginRules: {
        username: [{ required: true, trigger: 'blur', validator: validateUsername }],
        password: [
          { required: true, trigger: 'blur' },
          { min: 6, max: 12, trigger: 'blur', message: '密码长度应该在6-12位之间' }]
      },

**utils/validate.js**是一个专门存放校验工具方法的文件

关于修饰符

关于修饰符

在该页面中,我们发现了事件的几个修饰符 @keyup.enter.native @click.native.prevent

@keyup.**enter**属于按键修饰符,如果我们想监听在按回车键的时候触发,可以如下编写

<!-- 只有在 `key` 是 `Enter` 时调用 `vm.submit()` -->
<input v-on:keyup.enter="submit">

@keyup.enter.native 表示监听组件的原生事件,比如 keyup就是于input的原生事件,这里写native表示keyup是一个原生事件

Vue-Cli配置跨域代理

通过配置vue-cli的代理解决跨域访问的问题

为什么会出现跨域?

当下,最流行的就是**前后分离项目,也就是前端项目后端接口并不在一个域名之下,那么前端项目访问后端接口必然存在跨域**的行为.

怎么解决这种跨域 ?

请注意**,我们所遇到的这种跨域是位于开发环境的,真正部署上线时的跨域是生产环境**的

解决开发环境的跨域问题

开发环境的跨域

开发环境的跨域,也就是在**vue-cli脚手架环境下开发启动服务时,我们访问接口所遇到的跨域问题,vue-cli为我们在本地开启了一个服务,可以通过这个服务帮我们代理请求**,解决跨域问题

这就是vue-cli配置webpack的反向代理

采用vue-cli的代理配置

vue-cli的配置文件即**vue.config.js**,这里有我们需要的 代理选项

module.exports = {
  devServer: {
   // 代理配置
    proxy: {
        // 这里的api 表示如果我们的请求地址有/api的时候,就出触发代理机制
        // localhost:8888/api/abc  => 代理给另一个服务器
        // 本地的前端  =》 本地的后端  =》 代理我们向另一个服务器发请求 (行得通)
        // 本地的前端  =》 另外一个服务器发请求 (跨域 行不通)
        '/api': {
        target: 'www.baidu.com', // 我们要代理的地址
        changeOrigin: true, // 是否跨域 需要设置此值为true 才可以让本地服务代理我们发出请求
         // 路径重写
        pathRewrite: {
            // 重新路由  localhost:8888/api/login  => www.baidu.com/api/login
            '^/api': '' // 假设我们想把 localhost:8888/api/login 变成www.baidu.com/login 就需要这么做 
        }
      },
    }
  }
}

以上就是我们在vue-cli项目中配置的代理设置

接下来,我们在代码中将要代理的后端地址变成 后端接口地址

 // 代理跨域的配置
    proxy: {
      // 当我们的本地的请求 有/api的时候,就会代理我们的请求地址向另外一个服务器发出请求
      '/api/private/v1/': {
       target: 'http://127.0.0.1:8888', // 跨域请求的地址
        changeOrigin: true // 只有这个值为true的情况下 才表示开启跨域
      }
    }

本节注意**:我们并没有进行pathRewrite,因为后端接口就是ihrm-java.itheima.net/api**这种格式,所以不需要重写

**vue.config.js**的改动如果要生效,需要进行重启服务

同时,还需要注意的是,我们同时需要注释掉 mock的加载,因为mock-server会导致代理服务的异常

// before: require('./mock/mock-server.js'),  // 注释mock-server加载

生产环境的跨域

生产环境表示我们已经开发完成项目,将项目部署到了服务器上,这时已经没有了vue-cli脚手架的**辅助了,我们只是把打包好的html+js+css交付运维人员,放到Nginx服务器而已,所以此时需要借助Nginx**的反向代理来进行

server{
    # 监听9099端口
    listen 9099;
    # 本地的域名是localhost
    server_name localhost;
    #凡是localhost:9099/api这个样子的,都转发到真正的服务端地址http://baidu.com
    location ^~ /api {
        proxy_pass http://baidu.com;
    }    
}

注意:这里的操作一般由运维人员完成,需要前端进行操作,这里我们进行一下简单了解

更多正向代理和反向代理知识,请阅读这篇文章Nginx反向代理

提交代码

本节注意**:我们并没有进行pathRewrite,因为后端接口就是127.0.0.1:8888/api**这种格式,所以不需要重写

本节任务: 配置vue-cli的反向代理,实现后端接口的跨域访问

封装单独的登录接口

在单独请求模块中,单独封装登录接口

完成登录模块之后,我们需要对登录接口进行封装

首先,查阅接口文档中的登录接口

基础模板已经有了原来的登录代码,我们只需要进行简单的改造即可

export function login(data) {
  // 返回一个axios对象 => promise  // 返回了一个promise对象
  return request({
    url: 'login', // 因为所有的接口都要跨域 表示所有的接口要带 /api
    method: 'post',
    data
  })
}

如图

image-20210503141217425

封装Vuex的登录Action并处理token

在vuex中封装登录的action,并处理token

在这个小节中,我们将在vuex中加入对于用户的登录的处理

在Vuex中对token进行管理

在传统模式中,我们登录的逻辑很简单,如图

image-20200812003821680

上图中,组件直接和接口打交道,这并没有什么问题,但是·用的**钥匙**,我们需要让vuex来介入,将用户的token状态共享,更方便的读取,如图

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-d8PDvgxz-1621567701041)(http://blog.daqitc.net/vue/vue-admin-tempate/assets/image-20200812011826021.png)]

实现store/modules/user.js基本配置

// 状态
const state = {}
// 修改状态
const mutations = {}
// 执行异步
const actions = {}
export default {
  namespaced: true,
  state,
  mutations,
  actions
}

设置token的共享状态

const state = {
  token: null
}

我们需要知道,**钥匙**不能每次都通过登录获取,我们可以将token放置到本地的缓存中

在**utils/auth.js中,基础模板已经为我们提供了获取token,设置token,删除token**的方法,可以直接使用

只需要将存储的key放置成特定值即可

// import Cookies from 'js-cookie'

const TokenKey = 'xiaoyou'

export function getToken() {
  // return Cookies.get(TokenKey)
  return localStorage.getItem(TokenKey)
}

export function setToken(token) {
  // return Cookies.set(TokenKey, token)
  return localStorage.setItem(TokenKey, token)
}

export function removeToken() {
  // return Cookies.remove(TokenKey)
  return localStorage.removeItem(TokenKey)
}

初始化token状态 - store/modules/user.js

import { getToken, setToken, removeToken } from '@/utils/auth'
// 状态
// 初始化的时候从缓存中读取状态 并赋值到初始化的状态上
// Vuex的持久化 如何实现 ? Vuex和前端缓存相结合
const state = {
  token: getToken() // 设置token初始状态   token持久化 => 放到缓存中
}

提供修改token的mutations

// 修改状态
const mutations = {
  // 设置token
  setToken(state, token) {
    state.token = token // 设置token  只是修改state的数据  123 =》 1234
    // vuex变化 => 缓存数据
    setToken(token) // vuex和 缓存数据的同步
  },
  // 删除缓存
  removeToken(state) {
    state.token = null // 删除vuex的token
    removeToken() // 先清除 vuex  再清除缓存 vuex和 缓存数据的同步
  }
}
封装登录的Action

封装登录的action

登录action要做的事情,调用登录接口,成功后设置token到vuex,失败则返回失败

// 执行异步
const actions = {
  // 定义login action  也需要参数 调用action时 传递过来的参数
  async login(context, data) {
    const result = await login(data) // 实际上就是一个promise  result就是执行的结果
    // axios默认给数据加了一层data
    if (result.data.success) {
      // 表示登录接口调用成功 也就是意味着你的用户名和密码是正确的
      // 现在有用户token
      // actions 修改state 必须通过mutations
      context.commit('setToken', result.data.data)
    }
  }
}

上述代码中,我们使用了**async/await语法,如果用then**语法也是可以的

 // 为什么async/await 不用返回new Promise,因为 async函数本身就是 Promise,promise的值返回的值  
login(context, data) {
    return new Promise(function(resolve) {
      login(data).then(result => {
        if (result.data.success) {
          context.commit('setToken',  result.data.data) // 提交mutations设置token
          resolve()  // 表示执行成功了
        }
      })
    })
  }

以上两种写法都是OK的,我们在项目研发过程中,尽可能的采用前一种

除此之外,为了更好的让其他模块和组件更好的获取token数据,我们可以在**store/getters.js**中将token值作为公共的访问属性放出

const getters = {
  sidebar: state => state.app.sidebar,
  device: state => state.app.device,
  token: state => state.user.token // 在根级的getters上 开发子模块的属性给别人看 给别人用
}
export default getters

提交代码

通过本节内容,我们要掌握在Vuex中如何来管理共享状态

image-20200826145500269

本节任务:封装Vuex的登录Action并处理token

request中环境变量和异常的处理

设置request环境变量和异常处理

区分axios在不同环境中的请求基础地址

为什么会有环境变量之分? 如图

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-iifg4APs-1621567701042)(http://blog.daqitc.net/vue/vue-admin-tempate/assets/image-20200826150136697.png)]

从上图可以看出,开发环境实际上就是在自己的本地开发或者要求不那么高的环境,但是一旦进入生产,就是**真实的数据**。 拿银行作比喻,如果你在开发环境拿生产环境的接口做测试,银行系统就会发生很大的风险。

前端主要区分两个环境,开发环境,生产环境

也就是两个环境发出的请求地址是不同的,用什么区分呢?

环境变量

$ process.env.NODE_ENV # 当为production时为生产环境 为development时为开发环境

环境文件

我们可以在**.env.development.env.production**定义变量,变量自动就为当前环境的值

基础模板在以上文件定义了变量**VUE_APP_BASE_API,该变量可以作为axios请求的baseURL**

我们会发现,在模板中,两个值分别为**/dev-api/prod-api**

但是我们的开发环境代理是**/api**,所以可以统一下

# 开发环境的基础地址和代理对应
VUE_APP_BASE_API = '/api'
# 这里配置了/api,意味着需要在Nginx服务器上为该服务配置 nginx的反向代理对应/prod-api的地址 
VUE_APP_BASE_API = '/prod-api'  

本节注意:我们这里生产环境和开发环境设置了不同的值,后续我们还会在生产环境部署的时候,去配置该值所对应的反向代理,反向代理指向哪个地址,完全由我们自己决定,不会和开发环境冲突

在request中设置baseUrl

const service = axios.create({
  // 如果执行 npm run dev  值为 /api 正确  /api 这个代理只是给开发环境配置的代理
  // 如果执行 npm run build 值为 /prod-api  没关系  运维应该在上线的时候 给你配置上 /prod-api的代理
  baseURL: process.env.VUE_APP_BASE_API, // 设置axios请求的基础的基础地址
  timeout: 5000 // 定义5秒超时
}) // 创建一个axios的实例
处理axios的响应拦截器

OK,除此之外,axios返回的数据中默认增加了一层**data的包裹**,我们需要在这里处理下

并且,人资项目的接口,如果执行失败,只是设置了**successfalse**,并没有reject,我们需要一并处理下

处理逻辑如图

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-5XLMTZzb-1621567701043)(http://blog.daqitc.net/vue/vue-admin-tempate/assets/image-20200812020656210.png)]

// 响应拦截器
service.interceptors.response.use(response => {
  // axios默认加了一层data
  const { success, message, data } = response.data
  //   要根据success的成功与否决定下面的操作
  if (success) {
    return data
  } else {
    // 业务已经错误了 还能进then ? 不能 ! 应该进catch
    Message.error(message) // 提示错误消息
    return Promise.reject(new Error(message))
  }
}, error => {
  Message.error(error.message) // 提示错误信息
  return Promise.reject(error) // 返回执行错误 让当前的执行链跳出成功 直接进入 catch
})

既然在request中已经默认去除了一层data的外衣,所以我们也将上节login的action进行一下改动

处理登录的返回结构问题

  async login(context, data) {
    // 经过响应拦截器的处理之后 这里的result实际上就是 token
    const result = await login(data) // 实际上就是一个promise  result就是执行的结果
    // axios默认给数据加了一层data
    // 表示登录接口调用成功 也就是意味着你的用户名和密码是正确的
    // 现在有用户token
    // actions 修改state 必须通过mutations
    context.commit('setToken', result)
  }

提交代码

本节任务: 完成request环境变量和异常的处理

登录页面调用登录action,处理异常

目标 调用vuex中的登录action,并跳转到主页

按照如图的业务逻辑,把剩下的内容在登录页面引入

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-jD41ZIIK-1621567701043)(http://blog.daqitc.net/vue/vue-admin-tempate/assets/image-20200812011826021.png)]

引入actions辅助函数

import { mapActions } from 'vuex'  // 引入vuex的辅助函数

引入action方法

此处,我们采用直接引入模块action的方式,后面我们采用分模块的引用方式

methods: {
    ...mapActions(['user/login'])
}

调用登录

  this.$refs.loginForm.validate(async isOK => {
        if (isOK) {
          try {
            this.loading = true
            // 只有校验通过了 我们才去调用action
            await this['user/login'](this.loginForm)
            // 应该登录成功之后
            // async标记的函数实际上一个promise对象
            // await下面的代码 都是成功执行的代码
            this.$router.push('/')
          } catch (error) {
            console.log(error)
          } finally {
            //  不论执行try 还是catch  都去关闭转圈
            this.loading = false
          }
        }
      })

提交代码

注意:我们调用的是Vuex中子模块的action,该模块我们进行了namespaced: true,所以引用aciton时需要带上**user/, 并且在使用该方法时,直接使用 **this['user/login'], 使用this.user/login 语法是错误的

主页模块

路由页面整理

目标 删除基础模板中附带的多余页面

基础模板帮我们提前内置了一些页面,本章节我们进行一下整理

页面设置

首先,我们需要知道类似这种大型中台项目的页面路由是如何设置的。

简单项目

image-20200827153753307

当前项目结构

image-20200827155342126

为什么要拆成若干个路由模块呢?

因为复杂中台项目的页面众多,不可能把所有的业务都集中在一个文件上进行管理和维护,并且还有最重要的,前端的页面中主要分为两部分,一部分是所有人都可以访问的, 一部分是只有有权限的人才可以访问的,拆分多个模块便于更好的控制

静态路由和动态路由

image-20200716150421791

注意**这里的动态路由并不是 **路由传参的动态路由

路由和组件删除

了解完成路由设计之后,我们对当前的路由进行一下整理

删除多余的静态路由表 src/router/index.js

/**
 * constantRoutes
 * a base page that does not have permission requirements
 * all roles can be accessed
 */
export const constantRoutes = [
  {
    path: '/login',
    component: () => import('@/views/login/index'),
    hidden: true
  },

  {
    path: '/404',
    component: () => import('@/views/404'),
    hidden: true
  },

  {
    path: '/',
    component: Layout,
    redirect: '/dashboard',
    children: [{
      path: 'dashboard',
      name: 'Dashboard',
      component: () => import('@/views/dashboard/index'),
      meta: { title: 'Dashboard', icon: 'dashboard' }
    }]
  },

  // 404 page must be placed at the end !!!
  { path: '*', redirect: '/404', hidden: true }
]

原来的八个路由记录,我们只对上面几个进行保留

同时删除对应的无用组件

左侧导航菜单的最终样子

image-20210506105717518

同样的在api目录下,存在多余的api-table.js 一并删除

业务模块页面的快速搭建

目标: 掌握vue-admin-tempate 基础框架下新模块的创建

新建模块的页面

接下来,我们可以将小优电商后台关系系统需要做的模块快速搭建相应的页面和路由

├── dashboard           # 首页
├── login               # 登录
├── 404                 # 404
├── Users                 # 用户
├── Roles               # 角色
├── Rights              # 权限
├── Goods               # 商品
├── Category            # 类别
├── Report                # 报表

根据上图中的结构,在views目录下,建立对应的目录,给每个模块新建一个**index.vue**,作为每个模块的主页

每个模块的内容,可以先按照标准的模板建立,如

用户

<template>
  <div class="dashboard-container">
    <div class="app-container">
      <h2>
        用户
      </h2>
    </div>
  </div>
</template>

<script>
export default {

}
</script>

<style>

</style>
新建路由

根据以上的标准建立好对应页面之后,接下来建立每个模块的路由规则

在 router 目录下新建目录 modules

在此目录中新建各个路由模块

路由模块目录结构

image-20210506111546287

设置每个模块的路由规则

每个模块导出的内容表示该模块下的路由规则

如用户 user.js

// 导出属于用户的路由规则
import Layout from '@/layout'
//  {  path: '', component: '' }
// 每个子模块 其实 都是外层是layout  组件位于layout的二级路由里面
export default {
  path: '/user', // 路径
  name: '', // 给路由规则加一个name
  component: Layout, // 组件
  // 配置二级路的路由表
  children: [{
    path: '', // 这里当二级路由的path什么都不写的时候 表示该路由为当前二级路由的默认路由
    name: 'user', // 给路由规则加一个name
    component: () => import('@/views/Users'),
    // 路由元信息  其实就是存储数据的对象 我们可以在这里放置一些信息
    meta: {
      title: '用户管理' // meta属性的里面的属性 随意定义 但是这里为什么要用title呢, 因为左侧导航会读取我们的路由里的meta里面的title作为显示菜单名称
    }
  }]
}

// 当你的访问地址 是 /user的时候 layout组件会显示 此时 你的二级路由的默认组件  也会显示

上述代码中,我们用到了meta属性,该属性为一个对象,里面可放置自定义属性,主要用于读取一些配置和参数,并且值得**注意的是:我们的meta写了二级默认路由上面,而不是一级路由,因为当存在二级路由的时候,访问当前路由信息访问的就是二级默认路由**

静态路由和动态路由临时合并,形成左侧菜单

目标: 将静态路由和动态路由的路由表进行临时合并

什么叫临时合并?

前面讲过,动态路由是需要权限进行访问的,但是权限的动态路由访问是很复杂的,我们可以先将 静态路由和动态路由进行合并,不考虑权限问题,后面再解决这个问题

路由主文件 src/router/index.js

// 引入多个模块的规则
import Layout from '@/layout'
import userRouter from './modules/user'
import roleRouter from './modules/role'
import rightsRouter from './modules/right'
import goodsRouter from './modules/goods'
import categoryRouter from './modules/category'
import reportsRouter from './modules/report'


// 动态路由
export const asyncRoutes = [
  userRouter, roleRouter, rightsRouter, goodsRouter, categoryRouter, reportsRouter
]
const createRouter = () => new Router({
  // mode: 'history', // require service support
  scrollBehavior: () => ({ y: 0 }), // 管理滚动行为 如果出现滚动 切换就让 让页面回到顶部
  routes: [...constantRoutes, ...asyncRoutes] // 临时合并所有的路由
})

通过上面的操作,我们将静态路由和动态路由进行了合并

image-20200827170403463

当我们合并权限完成,我们惊奇的发现页面效果已经左侧的导航菜单 =》 路由页面

这是之前基础模板中对于左侧导航菜单的封装

image-20210506113926617

左侧菜单的显示逻辑,设置菜单图标

目标 解析左侧菜单的显示逻辑, 设置左侧导航菜单的图标内容

上小节中,我们集成了路由,菜单就显示内容了,这是为什么 ?

阅读左侧菜单代码

我们发现如图的逻辑

image-20200828103109646

由于,该项目不需要二级菜单的显示,所以对代码进行一下处理,只保留一级菜单路由

src/layout/components/Sidebar/SidebarItem.vue

<template>
  <div v-if="!item.hidden">
    <template v-if="hasOneShowingChild(item.children,item) && (!onlyOneChild.children||onlyOneChild.noShowingChildren)&&!item.alwaysShow">
      <app-link v-if="onlyOneChild.meta" :to="resolvePath(onlyOneChild.path)">
        <el-menu-item :index="resolvePath(onlyOneChild.path)" :class="{'submenu-title-noDropdown':!isNest}">
          <item :icon="onlyOneChild.meta.icon||(item.meta&&item.meta.icon)" :title="onlyOneChild.meta.title" />
        </el-menu-item>
      </app-link>
    </template>
    
    <!-- <el-submenu v-else ref="subMenu" :index="resolvePath(item.path)" popper-append-to-body>
      <template slot="title">
        <item v-if="item.meta" :icon="item.meta && item.meta.icon" :title="item.meta.title" />
      </template>
      <sidebar-item
        v-for="child in item.children"
        :key="child.path"
        :is-nest="true"
        :item="child"
        :base-path="resolvePath(child.path)"
        class="nest-menu"
      />
    </el-submenu> -->
  </div>
</template>

本节注意:通过代码发现,当路由中的属性**hidden**为true时,表示该路由不显示在左侧菜单中

与此同时,我们发现左侧菜单并不协调,是因为缺少图标。在本项目中,我们的图标采用了SVG的组件

左侧菜单的图标实际上读取的是meta属性的icon,这个icon需要我们提前放置在**src/icons/svg**目录下

项目提供了一些 svg 图标,具体的icon名称可参考线上地址,如果没有找到合适的,可以到 iconfont 获取

模块对应icon

├── category           # category
├── goods                # goods
├── reports            # reports
├── user               # account
├── roles              # roles
├── rights             # rights

按照对应的icon设置图标

其他页面不在一一实现,具体请看上面的文档

打包上线

打包之前的路由模式

**目标**配置打包之前的路由模式

在SPA单页应用中,有两种路由模式

hash模式 : #后面是路由路径,特点是前端访问,#后面的变化不会经过服务器

history模式:正常的/访问模式,特点是后端访问,任意地址的变化都会访问服务器

开发到现在,我们一直都在用hash模式,打包我们尝试用history模式

改成history模式非常简单,只需要将路由的mode类型改成history即可

const createRouter = () => new Router({
  mode: 'history', // require service support
  scrollBehavior: () => ({ y: 0 }), // 管理滚动行为 如果出现滚动 切换就让 让页面回到顶部
  routes: [...constantRoutes] // 改成只有静态路由
})

假设我们的地址是这样的 www.xxxx/com/hr/a www.xxxx/com/hr/b

我们会发现,其实域名是**www.xxxx/com**,hr是特定的前缀地址,此时我们可以配置一个base属性,配置为hr

const createRouter = () => new Router({
  mode: 'history', // require service support
  base: '/hr/', // 配置项目的基础地址
  scrollBehavior: () => ({ y: 0 }), // 管理滚动行为 如果出现滚动 切换就让 让页面回到顶部
  routes: [...constantRoutes] // 改成只有静态路由
})

此时,我们会发现地址已经变成我们想要的样子了

image-20200804014626686

提交代码

性能分析和CDN的应用

目标: 对开发的应用进行性能分析和CDN的应用

性能分析

我们集成了 功能,写了很多组件,最终都会打包成一堆文件,那么真实运行的性能如何呢?

我们可以使用vue-cli本身提供的性能分析工具,对我们开发的所有功能进行打包分析

它的应用非常简单

$ npm run preview -- --report

这个命令会从我们的**入口main.js**进行依赖分析,分析出最大的包,方便我们进行观察和优化

执行完这个命令,我们会看到如下的页面

image-20200804015849396

如图所以,方块越大,说明该文件占用的文件越大,文件越大,对于网络带宽和访问速度的要求就越高,这也就是我们优化的方向

像这种情况,我们怎么优化一下呢

webpack排除打包

CDN是一个比较好的方式

文件不是大吗?我们就不要把这些大的文件和那些小的文件打包到一起了,像这种xlsx,element这种功能性很全的插件,我们可以放到CDN服务器上,一来,减轻整体包的大小,二来CDN的加速服务可以加快我们对于插件的访问速度

使用方式

先找到 vue.config.js, 添加 externalswebpack 不打包 xlsxelement

vue.config.js

 // 排除 elementUI xlsx  和 vue 
  externals:
      {
        'vue': 'Vue',
        'element-ui': 'ELEMENT',
        'xlsx': 'XLSX'
     }

再次运行,我们会发现包的大小已经大幅减小

CDN文件配置

但是,没有被打包的几个模块怎么处理?

可以采用CDN的方式,在页面模板中预先引入

vue.config.js

const cdn = {
  css: [
    // element-ui css
    'https://unpkg.com/element-ui/lib/theme-chalk/index.css' // 样式表
  ],
  js: [
    // vue must at first!
    'https://unpkg.com/vue/dist/vue.js', // vuejs
    // element-ui js
    'https://unpkg.com/element-ui/lib/index.js', // elementUI
      'https://cdn.jsdelivr.net/npm/xlsx@0.16.6/dist/jszip.min.js',
    'https://cdn.jsdelivr.net/npm/xlsx@0.16.6/dist/xlsx.full.min.js'
  ]
}

但是请注意,这时的配置实际上是对开发环境和生产环境都生效的,在开发环境时,没有必要使用CDN,此时我们可以使用环境变量来进行区分

let cdn = { css: [], js: [] }
// 通过环境变量 来区分是否使用cdn
const isProd = process.env.NODE_ENV === 'production' // 判断是否是生产环境
let externals = {}
if (isProd) {
  // 如果是生产环境 就排除打包 否则不排除
  externals = {
    // key(包名) / value(这个值 是 需要在CDN中获取js, 相当于 获取的js中 的该包的全局的对象的名字)
    'vue': 'Vue', // 后面的名字不能随便起 应该是 js中的全局对象名
    'element-ui': 'ELEMENT', // 都是js中全局定义的
    'xlsx': 'XLSX' // 都是js中全局定义的
  }
  cdn = {
    css: [
      'https://unpkg.com/element-ui/lib/theme-chalk/index.css' // 提前引入elementUI样式
    ], // 放置css文件目录
    js: [
      'https://unpkg.com/vue/dist/vue.js', // vuejs
      'https://unpkg.com/element-ui/lib/index.js', // element
      'https://cdn.jsdelivr.net/npm/xlsx@0.16.6/dist/xlsx.full.min.js', // xlsx 相关
      'https://cdn.jsdelivr.net/npm/xlsx@0.16.6/dist/jszip.min.js' // xlsx 相关
    ] // 放置js文件目录
  }
}

image.png

注入CDN文件到模板

之后通过 html-webpack-plugin注入到 index.html之中:

config.plugin('html').tap(args => {
  args[0].cdn = cdn
  return args
})

找到 public/index.html。通过你配置的CDN Config 依次注入 css 和 js。

<head>
  <!-- 引入样式 -->
  <% for(var css of htmlWebpackPlugin.options.cdn.css) { %>
    <link rel="stylesheet" href="<%=css%>">
  <% } %>
</head>

<!-- 引入JS -->
<% for(var js of htmlWebpackPlugin.options.cdn.js) { %>
  <script src="<%=js%>"></script>
<% } %>

最后,进行打包

$ npm run build:prod

在nodejs环境中应用并代理跨域

**目标**将打包好的代码打包上线,并在nodejs中代理跨域

使用koa框架部署项目

到现在为止,我们已经完成了一个前端工程师的开发流程,按照常规的做法,此时,运维会将我们的代码部署到阿里云的ngix服务上,对于我们而言,我们可以将其部署到本机的nodejs环境中

部署 自动化部署 /手动部署

第一步,建立web服务文件夹 hrServer

$ mkdir hrServer #建立hrServer文件夹 

第二步,在该文件夹下,初始化npm

$ npm init -y

第三步,安装服务端框架koa(也可以采用express或者egg)

$ npm i koa koa-static

第四步,拷贝上小节打包的dist目录到**hrServer/public**下

第五步,在根目录下创建app.js,代码如下

const Koa  = require('koa')
const serve = require('koa-static');

const app = new Koa();
app.use(serve(__dirname + "/public")); //将public下的代码静态化
app.listen(3333, () => {
     console.log('人资项目启动')
})

此时,我们可以访问,http://localhost:3333

页面出来了

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-A2edXgDM-1621567701050)(http://blog.daqitc.net/vue/vue-admin-tempate/assets/image-20200805012430884.png)]

解决history页面访问问题

但是,此时存在两个问题,

  1. 当我们刷新页面,发现404

这是因为我们采用了history的模式,地址的变化会引起服务器的刷新,我们只需要在app.js对所有的地址进行一下处理即可

安装 koa中间件

$ npm i koa2-connect-history-api-fallback #专门处理history模式的中间件

注册中间件

const Koa  = require('koa')
const serve = require('koa-static');
const  { historyApiFallback } = require('koa2-connect-history-api-fallback');
const path = require('path')
const app = new Koa();
// 这句话 的意思是除接口之外所有的请求都发送给了 index.html
app.use(historyApiFallback({ 
     whiteList: ['/prod-api']
 }));  // 这里的whiteList是 白名单的意思
app.use(serve(__dirname + "/public")); //将public下的代码静态化

app.listen(3333, () => {
     console.log('人资项目启动')
})
解决生产环境跨域问题
  1. 当点击登录时,发现接口404

前面我们讲过,vue-cli的代理只存在于开发期,当我们上线到node环境或者ngix环境时,需要我们再次在环境中代理

在nodejs中代理

安装跨域代理中间件

$ npm i koa2-proxy-middleware

配置跨越代理

const proxy = require('koa2-proxy-middleware')

app.use(proxy({
  targets: {
    // (.*) means anything
    '/prod-api/(.*)': {
        target: 'http://ihrm-java.itheima.net/api', //后端服务器地址
        changeOrigin: true,
        pathRewrite: {     
            '/prod-api': ""
        }
    }
  }
}))

注意:这里之所以用了pathRewrite,是因为生产环境的请求基础地址是 /prod-api,需要将该地址去掉

此时,我们的项目就可以跨域访问了!

到现在为止,我们在十几天的时间里,完成了一个较为复杂的中台项目的解剖和开发,任何一个复杂的项目都是各种各样的功能通过合理的设计和布局组装而成的,所以未来我们每个同学要掌握的能力就是不论项目的难度和复杂度,都要学会技术的解耦和设计,这样我们的开发能力就会变得越来越强

Logo

前往低代码交流专区

更多推荐