持续学习中…

版本管理

V1.0.0

  • 2021-07-01
  • 基础

------ 基础 ------

1、布局

1.1 app-main

1.2 router-view

  • 使用同一个component的页面切换默认不会触发vue的created或者mounted钩子
    • 传统:可以通过watch $route的变化来进行处理
    • 简单:在router-view上加一个唯一key,保证路由切换都会重新渲染触发钩子
<router-view :key="key"></router-view>
   computed: {
       key() {
           return this.$route.fullPath
       }
   }
  • 或者:用两个不同的vue文件,但是引入相同的component

1.3 移动端

  • 对于管理后台这种重交互的项目是不能通过简单的适配来满足桌面端和移动端两端不同的交互,如果真的要做移动版后台,需要重做一套系统。

2、路由和侧边栏

  • 侧边栏和路由绑定在一起,会根据在@/router/index.js下配置的路由动态生成侧边栏

2.1 配置项

  • hidden:true // 设置后该路由将不会出现在侧边栏
  • redirect:‘noRedirect’ // 该导航在面包屑导航中不可被点击
  • alwaysShow: true // 一直显示根路由
  • name:‘route-name’// 设置路由名字,一定要填写,不然使用keep-alive时会出问题
  • meta:
    • roles: [‘admin’, ‘editor’] // 进入该路由的权限
    • title: ‘title’ // 该路由在侧边栏和面包屑中展示的名字
    • icon: ‘svg-name’ // 图标
    • noCache: true // 设置为true则不会被keep-alive缓存
    • breadcrumb: false // 设置为false则不会在面包屑中显示
    • affix: true // 设置为true则会在tag-view中固定显示
    • activeMenu: ‘route-name’ // 设置高亮的侧边栏路由

2.2 路由

  • constantRoutes: 不需要动态判断条件的路由,如登录页
  • asyncRoutes: 需要动态判断条件并通过addRoutes动态添加的页面
    注意:404页面一定要最后加载才不会使得后面的页面都被拦截到404页面

2.3 侧边栏

  • 基于el-menu(element)
  • 侧边栏是通过路由结合权限判断动态生成的
  • 支持路由无限嵌套,所以使用了递归组件
  • 修改侧边栏样式:’@/styles/sidebar.scss’
  • 侧边栏的两种形式:
    • submenu 直接的一个链接
    • el-menu-item 嵌套子菜单
  • sidebar:(默认规则)当children>1时,默认嵌套模式;当刚好只有一个时就会作为根路由显示在侧边栏(可以设置alwaysShow: true)
    unique-opened: 可以在Sidebar/index.vue中设置来控制侧边栏只有一个子菜单的展开

2.4 多级目录(嵌套路由)

  • 三级路由嵌套,需要手动在二级目录的根文件下添加一个,原则上有多少级路由就需要多少个

2.5 点击侧边栏,刷新当前路由

  • 方案一:query变化使view刷新(需要在router-view上加一个唯一key),弊端,每次都会带上query后缀
  • 方案二:先跳转到一个Redirect的页面,再(在beforeCreate())重定向到重复页面

2.6 面包屑

  • 通过watch $route变化动态生成
  • 和menu一样可以通过之前的配置项控制一些路由在面包屑中的展现
    可以通过breadcrumb: false让该路由不在面包屑中显示

2.7 侧边栏滚动问题

  • 之前版本的滚动都是css实现的
  • 缺点:兼容性问题,在火狐和其他低版本浏览器中不美观
  • 在侧边栏收起的情况,受限于elment-ui的menu组件实现方式,不能用上述方式
  • 现版本使用el-scrollbar来处理侧边栏滚动问题

2.8 侧边栏 外链

  • 需要在path中填写合法路径

2.9 侧边栏默认展开

  • 首先找到侧边栏代码
  • 通过default-openeds进行设置,注意其中的path是submenu的route-path
<el-menu
    :default-openeds="['/example','/nested']" // 添加本行代码
    :default-active="activeMenu"
    :collapse="isCollapse"
    :background-color="variables.menuBg"
    :text-color="variables.menuText"
    :unique-opened="false"
    :active-text-color="variables.menuActiveText"
    :collapse-transition="false"
    mode="vertical"
  >
    <sidebar-item v-for="route in permission_routes" :key="route.path" :item="route" :base-path="route.path" />
</el-menu>

3、权限验证

  • 实现方式:通过当前用户权限去比对路由表,生成当前用户权限可访问的路由表,通过router.addRoutes动态挂载到router上去
  • 但实际中可能会需要这样:希望每个页面的权限可以动态配置,而不是预设写死
  • 实际实现方式:原理基本相同,多了一步,前端在后台前端通过tree控件等方式给每一个页面动态配置权限,之后将这份路由表存储在后端,当用户登录后,拿到用户的roles,前端通过这个去向后端请求可访问的路由表,从而动态生成可访问页面,之后就是router.addRoutes挂载

3.1 逻辑修改

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

3.2 指令权限

  • 实现按钮级别的权限判断
  • v-permission
  • 方便使用可以全局注册
<el-tag v-permission="['admin']">admin</el-tag>

<script>
import permission from '@/directive/permission/index.js'
export default {
    directives: {permission}
}
</script>
  • 局限:el-tab不能使用v-permission,可以使用v-if和全局权限判断函数来实现
<template>
  <el-tab-pane v-if="checkPermission(['admin'])" label="Admin">Admin can see this</el-tab-pane>
  <el-tab-pane v-if="checkPermission(['editor'])" label="Editor">Editor can see this</el-tab-pane>
  <el-tab-pane v-if="checkPermission(['admin','editor'])" label="Admin-OR-Editor">Both admin or editor can see this</el-tab-pane>
</template>

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

export default{
   methods: {
    checkPermission
   }
}
</script>

4、快捷导航(标签栏导航)

  • 考虑到之前iframe方式实现的多页面应用用户习惯问题
  • keep-alive和router-view来实现
  • @/layout/components/AppMain.vue
  • 原理:其实就是nav实现的另外一种方式,本质上还是一个个router-link,再监听路由$route的变化,判断当前页面是否需要重新加载或者已被缓存

4.1 visitedViews vs cachedViews

  • tags-view维护的两个数组
  • visitedViews:即tag集合,用户访问过的页面
  • cachedViews:实际keep-alive的路由,可以在配置路由时通过meta.noCache来设置此路由是否缓存,默认缓存
    注意keep-alive和router-view是强耦合的,且keep-alive的include匹配的是name,所以一定要写name(不写就不会被缓存),并且router和component的name需要保持一致,且name需要唯一(否则会有递归导致内存泄漏的问题)

4.2 缓存不适合场景

比如使用了相同组件的两个页面,因为component的name一样,而include根据名字缓存,这样就会出问题了

解决
1.直接使用keep-alive的缓存而不使用include,弊端是不能动态的删除缓存,最多只能设置一个最大的缓存limit
2.使用localStorage等手动处理缓存

4.3 Affix 固钉

  • 在meta中设置了该属性为true的tag会被固定在tags-view中(不可删除)

4.4 移除

  • keep-alive逻辑比较绕,如果不需要,建议移除此功能(注意要移除掉所有相关依赖)

5、新增页面

  • 首先在@/router/index.js中增加你需要添加的路由
  • 需要在他的children里面添加子路由,这样menu-item就会出现在侧边栏
  • 如果children下子路由的个数大于1,就会出现展开箭头和submenu了
  • 可以设置alwaysShow来忽视这个自动判断

5.1 多级目录(嵌套路由)

  • 如果路由是多级目录,如三级路由需要在二级目录文件中添加router-view
  • 原则上有多少级路由嵌套就需要多少个router-view

5.2 新增view

  • 新增完路由之后需要在@/views下创建对应的文件夹
  • 该模块下的功能组件或方法就建议在本文件夹下面创建一个utils或components文件夹
  • 各功能模块维护自己的utils或components

5.3 新增API

  • 最后在@/api文件夹下面创建本模块对应的API服务

5.4 新增组件

  • 在全局的@/component下一般会写一些全局能被公用的组件
  • 特定的业务组件写在对应的模块的component下
    请注意:拆分组件最大的好处不是公用而是可维护性

5.5 新增样式

  • 和组件一样,公共样式就写在@/styles下面
  • 其他样式就写在自己的style里面,注意要使用scoped或命名空间,避免污染全局

6、样式

  • scoped成功解决了全局样式污染的问题
  • 使用了scoped之后,父组件的样式不会渗透到子组件
  • 不过一个子组件根节点的样式还是会受到其父子组件的样式影响,这样的设定是为了方便布局

6.1 自定义element-ui的样式

  • 因为在页面中使用element-ui的组件,又想要在这个页面的样式中去覆盖全局安装的element-UI的样式,这样就不能用scoped了
  • 因为父组件样式没有办法影响到子组件
  • 但是不使用scoped的话又会影响到全局
    解决
    1.使用一个父级去包裹该样式
    2.使用深度选择器 >>> 或者 /deep/

6.2 postcss

  • vue-loader 的 postcss 会默认读取postcss.config.js

  • autoprefixer 会去读取 package.json 下 browserslist 的配置参数

    • 1% 兼容全球使用率大于 1%的浏览器

    • last 2 versions 兼容每个浏览器的最近两个版本
    • not ie <= 8 不兼容 ie8 及以下
// postcss.config.js
module.exports = {
  plugins: {
    autoprefixer: {}
  }
}

// package.json
"browserslist": [
    "> 1%",
    "last 2 versions",
    "not ie <= 8"
  ]

6.3 mixin

  • 本项目没有设置自动注入 sass 的 mixin 到全局,所以需要在使用的地方手动引入 mixin
  • 如需要自动将 mixin 注入到全局 ,可以使用sass-resources-loader
<style rel="stylesheet/scss" lang="scss">
  @import "src/styles/mixin.scss";
</style>

7、完成和服务端进行交互

7.1 前端请求交互

一个完整的前端 UI 交互到服务端处理流程是这样的:

  1. UI 组件交互操作;
  2. 使用封装的 request.js 发送请求;
  3. 获取服务端返回;
  4. 更新 data;
  • 统一的请求处理都放在 @/api 文件夹中,并且一般按照 model 纬度进行拆分文件

7.2 requst.js

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

7.3 设置多个baseURL

  • 通过环境变量设置多个baseURL,从而请求不同的 api 地址
# .env.development
VUE_APP_BASE_API = '/dev-api' #注入本地 api 的根路径
VUE_APP_BASE_API2 = '/dev-api2' #注入本地 api2 的根路径
  • 之后根据环境变量创建axios实例,让它具有不同的baseURL @/utils/request.js
// 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
})
  • 或者直接覆盖
export function fetchList(query) {
  return request({
    url: '/article/list',
    method: 'get',
    params: query,
    baseURL: 'xxxx' // 直接通过覆盖的方式
  })
}

8、mock data

  • mock数据是前后端分离的关键,通过模拟请求和逻辑,能让前端开发更加独立自主不被服务端开发阻塞

8.1 swagger

  • 后端模拟数据
  • 是一个 REST APIs 文档生成工具,它从代码注释中自动生成文档
  • 可以跨平台,开源,支持大部分语言,社区好

8.2 easy mock

  • 是一个纯前端可视化,并且能快速生成模拟数据的持久化服务
  • 非常的简单易用还能结合 swagger,天然支持跨域

8.3 mockjs

  • 原理是: 拦截了所有的请求并代理到本地,然后进行数据模拟,所以你会发现 network 中没有发出任何的请求
  • 最大的问题是就是它的实现机制:它会重写浏览器的XMLHttpRequest对象,从而才能拦截所有请求,代理到本地,但这导致一些底层依赖XMLHttpRequest的库都会和它发生不兼容
  • 另一个问题:因为是本地模拟数据不会走网络请求,所以调试很麻烦,只能console.log

8.4 新方案

  • 在本地会启动一个mock-server来模拟数据,线上环境还是继续使用mockjs来进行模拟
  • 不管是本地还是线上所有的数据模拟都是基于mockjs生成的,所以只要写一套 mock 数据,就可以在多环境中使用
  • mock 是完全基于webpack-dev-serve来实现的,所以在你启动前端服务的同时,mock-server就会自动启动
  • 是一个真正的server,所以可以通过控制台中的network,清楚的知道接口返回的数据结构
  • 并且同时解决了之前mockjs会重写 XMLHttpRequest对象,导致很多第三方库失效的问题
  • 本项目的所有请求都是通过封装的request.js进行发送的,所有的请求都设置了一个baseURL
  • 而这个baseURL又是通过读取process.env.VUE_APP_BASE_API这个环境变量来动态设置的,这样方便我们做到不同环境使用不同的 api 地址

8.5 移除

  • 只要在vue.config.js中移除webpack-dev-server中proxy和after这个Middleware就可以了
  • 请注意:该操作需要重启服务
  • mock-server只会在开发环境中使用,线上生产环境目前使用MockJs进行模拟。如果不需要请移除

8.6 新增

  • 如果你想添加 mock 数据,只要在根目录下找到mock文件,添加对应的路由,对其进行拦截和模拟数据即可
  • 先声明接口,找到对应的 mock 文件夹mock/article.js,在下面创建一个能拦截路由的 mock 接口
  • 请注意,mock 拦截是基于路由来做的,请确保 mock 数据一定能匹配你的 api 路由,支持正则
// fetchComments 的 mock
{
  // url 必须能匹配你的接口路由
  // 比如 fetchComments 对应的路由可能是 /article/1/comments 或者 /article/2/comments
  // 所以你需要通过正则来进行匹配
  url: '/article/[A-Za-z0-9]/comments',
  type: 'get', // 必须和你接口定义的类型一样
  response: (req, res) => {
    // 返回的结果
    // req and res detail see
    // https://expressjs.com/zh-cn/api.html#req
    return {
      code: 20000,
      data: {
        status: 'success'
      }
    }
  }
}

8.7 修改

  • 你本地模拟了了一些数据,待后端完成接口后,逐步替换掉原先 mock 的接口
  • 要在mock/role/index.js找到对应的路由,之后将它删除即可

8.8 多个server

  • 目前项目只启动了一个mock-server,当然你也可以有自己其它的mock-server或者代理接口
  • 可以一部分接口走这个服务,另一些接口走另一个服务。只需要将它们分别设置不同的的baseURL即可
  • 之后根据设置的 url 规则在 vue.config.js 中配置多个 proxy

8.9 启用纯前端 Mock

  • 在mock/index.js也封装了一个纯前端 mock 的方法,你只需要在src/main.js中:
import { mockXHR } from '../mock'
mockXHR()
  • 这样就会变成纯前端 mock 数据了

8.10 本地 Mock 数据与线上数据切换

8.10.1 Easy-Mock 的形式

  • 需要保证你本地模拟 api 除了根路径其它的地址是一致的
  • 可以通过之后会介绍的环境变量来做到不同环境下,请求不同的 api 地址
  • 之后根据环境变量创建axios实例,让它具有不同的baseURL。 @/utils/request.js
https://api-dev/login   // 本地请求

https://api-prod/login  // 线上请求


# .env.development
VUE_APP_BASE_API = '/dev-api' #注入本地 api 的根路径

# .env.production
VUE_APP_BASE_API = '/prod-api' #注入线上 api 的根路径

// create an axios instance
const service = axios.create({
  baseURL: process.env.BASE_API, // api 的 base_url
  timeout: 5000 // request timeout
})
  • 这样我们就做到了自动根据环境变量切换本地和线上 api

8.10.2 Mock.js 的切换

  • 我们本地使用 Mock.js 模拟本地数据,线上使用真实环境 api 方法
  • 主要是判断:是线上环境的时候,不引入 mock 数据就可以了,只有在本地引入 Mock.js
  • 只有在本地环境之中才会引入 mock 数据
// main.js
// 通过环境变量来判断是否需要加载启用
if (process.env.NODE_ENV === 'development') {
  require('./mock') // simulation data
}

9、引入外部模块

  • 引入依赖
  • 加上 --save 参数会自动添加依赖到 package.json 中去

9.1 使用

  • 全局注册
  • 局部注册
// 全局
import countTo from 'vue-count-to'
Vue.component('countTo', countTo)
<template>
  <countTo :startVal='startVal' :endVal='endVal' :duration='3000'></countTo>
</template>

// 局部
<template>
  <countTo :startVal='startVal' :endVal='endVal' :duration='3000'></countTo>
</template>

<script>
import countTo from 'vue-count-to';
export default {
  components: { countTo },
  data () {
    return {
      startVal: 0,
      endVal: 2017
    }
  }
}
</script>

9.2 在 vue 中优雅的使用第三方库

  • 在 Vuejs 项目中使用 JavaScript 库的一个优雅方式是将其代理到 Vue 的原型对象上去
// main.js
import moment from 'moment'
Object.defineProperty(Vue.prototype, '$moment', { value: moment })
  • 由于所有的组件都会从 Vue 的原型对象上继承它们的方法, 因此在所有组件/实例中都可以通过 this.$moment. 的方式访问 Moment 而不需要定义全局变量或者手动的引入

有的第三方组件没有VUE版本,这时候可以自行封装,在mounted里面初始化即可

10、构建和发布

# 打包正式环境
npm run build:prod

# 打包预发布环境
npm run build:stage
  • 构建打包成功之后,会在根目录生成 dist 文件夹,里面就是构建打包好的文件,通常是 ***.js 、***.css、index.html 等静态文件
  • 如果需要自定义构建,比如指定 dist 目录等,则需要通过 config的 outputDir 进行配置

10.1 环境变量

  • 所有测试环境或者正式环境变量的配置都在 .env.development等 .env.xxxx文件中
  • 它们都会通过 webpack.DefinePlugin 插件注入到全局

注意!!!

  • 环境变量必须以VUE_APP_为开头。如:VUE_APP_API、VUE_APP_TITLE
// 你在代码中可以通过如下方式获取:
console.log(process.env.VUE_APP_xxxx)

10.2 分析构建文件体积

  • 如果你的构建文件很大,你可以通过 webpack-bundle-analyzer 命令构建并分析依赖模块的体积分布,从而优化你的代码
  • npm run preview – --report
  • 运行之后你就可以在 http://localhost:9526/report.html 页面看到具体的体积分布
  • 强烈建议开启 gzip ,使用之后普遍体积只有原先 1/3 左右。打出来的 app.js 过大,查看一下是不是 Uglify 配置不正确或者 sourceMap 没弄对

10.3 发布

  • 只需要将最终生成的静态文件,也就是通常情况下 dist 文件夹的静态文件发布到你的 cdn 或者静态服务器即可
  • 其中的 index.html 通常会是你后台服务的入口页面,在确定了 js 和 css 的静态之后可能需要改变页面的引入路径
  • 部署时可能会发现资源路径不对 ,只需修改 vue.config.js 文件资源路径即可

10.4 前端路由与服务端的结合

  • vue-router可以选择两种方式:browserHistory 和 hashHistory
  • 两者的区别简单来说是对路由方式的处理不一样
    • hashHistory 是以 # 后面的路径进行处理,通过 HTML 5 History 进行前端路由管理
    • browserHistory 则是类似我们通常的页面访问路径,并没有 #,但要通过服务端的配置,能够访问指定的 url 都定向到当前页面,从而能够进行前端的路由管理
  • 本项目默认使用的是 hashHistory ,所以如果你的 url 里有 #
  • 想去掉的话,需要切换为 browserHistory
  • 修改 src/router/index.js 中的 mode 即可

  • 如果使用静态站点
    • hashHistory页面路径使用#开始,所有访问都在前端完成,可以正常访问应用
    • browserHistory,因为静态服务器并没有能映射的文件,所以可能会无法访问应用;如果有对应的后台服务器就可以用这种模式,只需要在服务端做一个映射

11、环境变量

  • .env文件在所有环境都会被载入
  • .env.[mode]只会在指定模式中被载入
  • 一个环境文件只包含环境文件的键值对
  • 注意!!环境变量必须以VUE_APP开头
  • 可以在代码中通过process.env.VUE_APP_XXX获取

  • 还有两个在应用代码中始终可用的变量
    • NODE_ENV:取决于应用运行模式,可能是production、development、test中的一种
    • BASE_URL:应用部署的基础路径,和vue.config.js中的publicPath相符合
  • 除了一些写在.env的环境变量之外,还有一些构建和部署相关的变量都是需要在vue.config.js中配置的
  • 可以通过process.env.NODE_ENV来执行判断环境,来设置不同的参数

------ 进阶 ------

1、跨域问题

  • 主要是两种:
    • cors(推荐):跨域资源共享,后端实现,配好之后所有接口不管环境项目都可以复用
    • 纯前端解决方案:dev开发模式下使用proxy(生产环境下不能使用),prod环境下使用nginx反向代理
  • cors原理:每一次请求,浏览器先以OPTIONS方式发送一个预请求,通过预请求从而获知服务器对跨域请求支持的HTTP方法
    • 在确认服务器允许该跨域请求的情况下,再以HTTP请求方法发送真正的请求
  • proxy和nginx实现原理相同:通过搭建一个中转服务器来转发请求规避跨域问题
GitHub 加速计划 / vu / vue-element-admin
87.26 K
30.42 K
下载
PanJiaChen/vue-element-admin: 是一个基于 Vue.js 和 Element UI 的后台管理系统模板,支持多种数据源和插件扩展。该项目提供了一个完整的后台管理系统模板,可以方便地实现后台管理系统的快速搭建和定制,同时支持多种数据源和插件扩展。
最近提交(Master分支:2 个月前 )
0caa975e - 2 年前
cd3f7267 - 2 年前
Logo

旨在为数千万中国开发者提供一个无缝且高效的云端环境,以支持学习、使用和贡献开源项目。

更多推荐