基於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>
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章