基于vue-cli3的vue权限框架搭建

环境搭建事先准备

本文只注重前端的搭建,后端只提及部分
本框架是基于vue-cli3搭建的,首先你需要安装vue-cli脚手架。
新手上路,有什么写错的请指点

  • 查看你的版本,cmd命令
vue -V
  • 创建你的项目(admin为项目名称)
vue create admin

然后安装你的各种负载,这里不进行详细说明
我的package.json可以进行参考

  "dependencies": {
    "axios": "^0.19.0",
    "babel-plugin-import": "^1.12.2",
    "core-js": "^3.3.2",
    "enquire.js": "^2.1.6",
    "less": "^3.10.3",
    "vue": "^2.6.10",
    "vue-apexcharts": "^1.5.1",
    "vue-router": "^3.1.3",
    "vuex": "^3.0.1"
  },
  "devDependencies": {
    "@vue/cli-plugin-babel": "^4.0.0",
    "@vue/cli-plugin-eslint": "^4.0.0",
    "@vue/cli-service": "^4.0.0",
    "@vue/eslint-config-standard": "^4.0.0",
    "babel-eslint": "^10.0.3",
    "babel-plugin-component": "^1.1.1",
    "eslint": "^5.16.0",
    "eslint-plugin-vue": "^5.0.0",
    "less-loader": "^5.0.0",
    "lint-staged": "^9.4.2",
    "stylus": "^0.54.7",
    "stylus-loader": "^3.0.2",
    "svg-sprite-loader": "^4.1.6",
    "vue-template-compiler": "^2.6.10"
  },
  • 配置vue.config.js
    在vue-cli3以后,目录结构十分精简,那么我们的配置写在哪里?
    官方文档有说明,在项目根目录下面新建vue.config.js文件
    以下我的配置,可以结合官网文档参考一下
const path = require('path') // 引入path模块
function resolve (dir) {
  return path.join(__dirname, dir) // path.join(__dirname)设置绝对路径
}

module.exports = {
  publicPath: '/', // 基本路径
  outputDir: 'dist', // 输出文件目录
  lintOnSave: true, // eslint-loader 是否在保存的时候检查
  productionSourceMap: true, // 生产环境是否生成 sourceMap 文件
  // css相关配置
  css: {
    extract: true, // 是否使用css分离插件 ExtractTextPlugin
    sourceMap: false, // 开启 CSS source maps
    modules: false,
    loaderOptions: {
      less: {
        modifyVars: {
          'primary-color': '#1DA57A',
          'link-color': '#1DA57A',
          'border-radius-base': '2px'
        },
        javascriptEnabled: true
      }
    }
  },
  chainWebpack: config => {
    // 配置路径别名
    config.resolve.alias
      .set('@', resolve('src'))
      .set('_c', resolve('src/components'))
      .set('_v', resolve('src/views'))
      .set('_u', resolve('src/utils'))
      .set('_m', resolve('src/common/mixin'))
      .set('_api', resolve('src/api'))
    config.module.rules.delete('svg') // 配置svg-sprite-loader,没有此需求请注释
    config.module
      .rule('svg-smart')
      .test(/\.svg$/)
      .include
      .add(resolve('src/assets/svgs'))
      .end()
      .use('svg-sprite-loader')
      .loader('svg-sprite-loader')
      .options({
        symbolId: 'icon-[name]'
      })
  },
  // webpack-dev-server 相关配置
  devServer: {
    open: false, // 是否在构建完成后打开默认浏览器
    host: '127.0.0.1', // 监听地址
    port: 8999, // 端口
    https: false, // 是否有必须通过https的服务
    hotOnly: false,
    proxy: {
      '/api': {
        target: 'url', //代理
        ws: true,
        changeOrigin: true,
        pathRewrite: {
          '^/api': ''
        }
      },
      '/foo': {
        target: '<other_url>'
      }
    },
    before: app => {}
  },
  // 第三方插件配置
  pluginOptions: {}
}

在这里插入图片描述
以上便是当前目录结构

  • 路由配置
    路由使用的是动态路由,分为两部分:不需要鉴权的和需要鉴权的

以下便是基础路由(不需要鉴权的):

import Vue from 'vue'
import Router from 'vue-router'

// 全局Router异常处理
const originalPush = Router.prototype.push
Router.prototype.push = function push (location) {
  return originalPush.call(this, location).catch(err => { if (typeof err !== 'undefined')console.log(err) })
}
Vue.use(Router)

let constRouter = [
  {
    path: '/login',
    name: '登录页',
    component: LoginView // 需要自行引入
  },
  { // 主界面,基础路由中先不指定组件
    path: '/',
    name: '主界面',
    redirect: '/home'
  },
    {
    path: '*',
    name: '404',
    component: 404// 需要自行引入
  },
]

let router = new Router({
  // mode: 'history',
  routes: constRouter
})

export default router

需要鉴权的路由,在登录时候,后台拼接返回路由
我们需要用到router.addRoutes来添加动态路由。
在登陆系统的时候,如果登录成功,然后返回一个SESSIONID或者JWT作为身份鉴权标识或者没有???(取决于你的后台,这里不过多叙述),然后重定向路由

		login({ // 登录请求,后面会讲到
              username: name,
              password: password
            }).then((r) => {
              this.$router.push('/') //重定向路由
            }).catch((e) => {
              console.error(e)
            })
  • 路由拦截配置

这部分借鉴github上面一个大佬的,找半天地址没找到

import router from './../router'
import localStore from '_u/localstorage' // 存到浏览器
import request from '_u/request' // axios请求

const whiteList = ['/login'] // 白名单

let asyncRouter // 新增路由

// 导航守卫,渲染动态路由
router.beforeEach((to, from, next) => {
  if (whiteList.indexOf(to.path) !== -1) {
    next()
  }
  let token = localStore.get('USER_TOKEN')
  let user = localStore.get('USER')
  let userRouter = get('USER_ROUTER')
  if (token.length && user) {
    if (!asyncRouter) {
      if (!userRouter) {
        request.get(`menu/${user.username}`).then((res) => {
          asyncRouter = res.data
          save('USER_ROUTER', asyncRouter)
          go(to, next)
        }).catch(err => { console.error(err) })
      } else {
        asyncRouter = userRouter
        go(to, next)
      }
    } else {
      next()
    }
  } else {
    next('/login')
  }
})

function go (to, next) {
  asyncRouter = filterAsyncRouter(asyncRouter)
  console.log(asyncRouter)
  router.addRoutes(asyncRouter)
  next({ ...to, replace: true })
}

function save (name, data) {
  localStorage.setItem(name, JSON.stringify(data))
}

function get (name) {
  return JSON.parse(localStorage.getItem(name))
}

function filterAsyncRouter (routes) {
  return routes.filter((route) => {
    let component = route.component
    if (component) {
      switch (route.component) {
        case 'MenuView':
          route.component = MenuView // 表示菜单页面
          break
        case 'PageView':
          route.component = PageView // 表示页面
          break
        case 'EmptyPageView':
          route.component = EmptyPageView //表示空白页面
          break
        case 'HomePageView':
          route.component = HomePageView //表示主界面
          break
        default:
          route.component = view(component) // 其他均为组件
      }
      if (route.children && route.children.length) {
        route.children = filterAsyncRouter(route.children)
      }
      return true
    }
  })
}

function view (path) {
  return function (resolve) {
    import(`@/views/${path}.vue`).then(mod => {
      resolve(mod)
    })
  }
}

刚刚登录请求重定向会被路由拦截器给拦截,判断用户是否有登录,没有登录就会请求后台获取权限(动态路由),然后存储在本地。接下来动态添加路由,这就完成了路由的动态添加。

router.addRoutes(asyncRouter) //上面有
  • axios的配置
    在上面其实已经用到了请求,下面讲解如何配置axios。
import axios from 'axios'
import { message, Modal, notification } from 'ant-design-vue' // 前端框架
import moment from 'moment'
import store from '../store' // vuex
import localstore from '_u/localstorage' // 本地存储
moment.locale('zh-cn') // 语言

// 统一配置
let REQUEST= axios.create({
  baseURL: 'http://127.0.0.1:9527/',  // 后台地址,如果启用api代理,在vue.config.js要进行修改
  responseType: 'json',
  validateStatus (status) {
    // 200 外的状态码都认定为失败
    return status === 200
  }
})

// 拦截请求
REQUEST.interceptors.request.use((config) => {
  if (config.url !== 'login') {
    let expireTime = store.state.account.expireTime
    let now = moment().format('YYYYMMDDHHmmss')
    if (now - expireTime >= -10) {
      Modal.error({
        title: '登录已过期',
        content: '很抱歉,登录已过期,请重新登录',
        okText: '重新登录',
        mask: false,
        onOk: () => {
          return new Promise((resolve, reject) => {
            localstore.clear()
            location.reload()
          }).catch(function (reason) {
            console.log('catch:', reason)
          })
        }
      })
    }
  }
  if (store.state.account.token) {
    config.headers.Authentication = store.state.account.token
  }
  return config
}, (error) => {
  return Promise.reject(error)
})

// 拦截响应
REQUEST.interceptors.response.use((config) => {
  return config
}, (error) => {
  if (error.response) {
    let errorMessage = error.response.data === null ? '系统内部异常,请联系网站管理员' : error.response.data.message
    switch (error.response.status) {
      case 404:
        notification.error({
          message: '系统提示',
          description: '很抱歉,资源未找到',
          duration: 4
        })
        break
      case 403:
      case 401:
        notification.warn({
          message: '系统提示',
          description: '没有相应权限或者登录已失效',
          duration: 4
        })
        localstore.clear()
        break
      default:
        notification.error({
          message: '系统提示',
          description: errorMessage,
          duration: 4
        })
        break
    }
  }
  return Promise.resolve(error)
})

export default request

然后请求格式:

import request from '_u/request' // 上面哪个axios实例

export function login (data) {
  return request({
	method: 'post',
	url: '/login',
	data: data
})
}

再补充一下目录结构
在这里插入图片描述

其他(按钮权限)

  • 按钮权限列表

如果你想给不同用户展示不同的按钮的时候(数据权限),你会用到这个。
哪个你后台不光需要返回动态路由,还需要返回按钮权限数组列表

在这里插入图片描述
这样的一个东西,然后存储再vuex(刷新会丢失),还要存储在本地

  • 自定义指令

    官方文档
    install.js 全局注册,注意要在main.js里面引用

import Vue from 'vue'

import { hasPermission, hasNoPermission} from '_u/permissionDirect'

const Plugins = [
  hasPermission,
  hasNoPermission,
  hasAnyPermission,
  hasRole,
  hasAnyRole
]

Plugins.map((plugin) => {
  Vue.use(plugin)
})

export default Vue
// 必须包含列出的所有权限,元素才显示
export const hasPermission = {
  install (Vue) {
    Vue.directive('hasPermission', {
      bind (el, binding, vnode) {
        let permissions = vnode.context.$store.state.account.permissions
        let value = binding.value
        let flag = true
        for (let v of value) {
          if (!permissions.includes(v)) {
            flag = false
          }
        }
        if (!flag) {
          if (!el.parentNode) {
            el.style.display = 'none'
          } else {
            el.parentNode.removeChild(el)
          }
        }
      }
    })
  }
}

// 当不包含列出的权限时,渲染该元素
export const hasNoPermission = {
  install (Vue) {
    Vue.directive('hasNoPermission', {
      bind (el, binding, vnode) {
        let permissions = vnode.context.$store.state.account.permissions
        let value = binding.value
        let flag = true
        for (let v of value) {
          if (permissions.includes(v)) {
            flag = false
          }
        }
        if (!flag) {
          if (!el.parentNode) {
            el.style.display = 'none'
          } else {
            el.parentNode.removeChild(el)
          }
        }
      }
    })
  }
}

看着很多,就是一句话,判断有没有这个权限,没有就不渲染这个按钮

使用就是下面这个样子

 <a-button @click="batchDelete" v-hasPermission="['user:delete']">删除</a-button>
发布了9 篇原创文章 · 获赞 8 · 访问量 450

猜你喜欢

转载自blog.csdn.net/qq_40822000/article/details/103275758