Vue中后台管理系统的权限控制

效果图

下载

    这里是用到的一些插件

npm install axios --save
npm install element-ui --save
npm install stylus --save
npm install stylus-loader --save

引入

     在 mian.js 中引入,并写入路由权限拦截

import Vue from 'vue'
import App from './App.vue'
import router from './router'
import store from './store'

// 引入 element-ui
import ElementUI from 'element-ui'
import 'element-ui/lib/theme-chalk/index.css'
Vue.use(ElementUI)

// 权限控制(这里没有做登陆拦截)
router.beforeEach((to, from, next) => {
  // 根据路由元属性requiresAuth判断改路由是否需要控权限
  if (to.matched.some(item => item.meta.requiresAuth)) {
    // 1、(首次访问页面)登录页面跳转过来、直接访问地址、刷新页面时,from.path为 "/",
    // 先请求权限接口,判断权限后再判断是否拦截
    // 2、已访问本系统但权限为空
    if (from.path === '/' || store.state.power_list.length === 0) {
      store.dispatch('POWER_LIST').then(res => {
        if (res.length === 0) {
          next('/error')
        } else {
          // 验证是否有访问该页面的权限
          store.dispatch('HAS_POWER', to).then(res => {
            res === true ? next() : next('/error')
          })
          // 设置导航菜单选中状态
          store.commit('SELECTITEM', to)
        }
      })
    } else {
      // 验证是否有访问该页面的权限
      store.dispatch('HAS_POWER', to).then(res => {
        res === true ? next() : next('/error')
      })
      // 设置导航菜单选中状态
      store.commit('SELECTITEM', to)
    }
  } else {
    next()
  }
})

new Vue({
  router,
  store,
  render: h => h(App)
}).$mount('#app')

Vuex文件(store.js)

     获取的用户权限列表存入这里

import Vue from 'vue'
import Vuex from 'vuex'
import axios from 'axios'

Vue.use(Vuex)

export default new Vuex.Store({
  state: {
    // 用户可以访问的url,用于判断访问权限
    power_list: [],
    // 导航列表选中项
    selectItem: ''
  },
  mutations: {
    // 设置权限列表
    SETPOWERLIST (state, list) {
      state.power_list = list
    },
    // 设置导航菜单选中状态
    SELECTITEM (state, to) {
      state.selectItem = to.path || window.location.pathname
    }
  },
  actions: {
    // 获取权限列表
    POWER_LIST ({ state, commit }, to) {
      return new Promise((resolve, reject) => {
        axios.get('/api/power.json').then(function (res) {
          let result = res.data.data
          let newArr = result.find((item) => {
            return item.userId === Number(sessionStorage.userId)
          })
          if (newArr && newArr.powerlist.length !== 0) {
            // 设置权限列表
            commit('SETPOWERLIST', newArr.powerlist)
            resolve(newArr.powerlist)
          } else {
            resolve([])
          }
        })
      })
    },
    // 判断是否有权限访问当前页面
    HAS_POWER (context, to) {
      return new Promise((resolve, reject) => {
        let flag = context.state.power_list.some(item => {
          return item.path.includes(to.path)  // 判断是否包含指定的路径
        })
        resolve(flag)
      })
    }
  }
})

路由文件(router.js)

     需要验证权限的路由都添加了 meta 属性

import Vue from 'vue'
import Router from 'vue-router'
import Login from './components/login.vue'
import Home from './components/Home.vue'
import Transfer from './components/transfer.vue'
import Error from './components/error.vue'

// 异步加载组件(按需加载)
const Album = () => import('./components/album.vue')
const BarChart = () => import('./components/barChart.vue')
const LineChar = () => import('./components/lineChart.vue')
const PieChart = () => import('./components/pieChart.vue')
const LiquidfillChart = () => import('./components/liquidfillChart.vue')
const WorldChart = () => import('./components/worldChart.vue')
const ChinaChart = () => import('./components/chinaChart.vue')
const ProvinceChart = () => import('./components/provinceChart.vue')
const ScatterChart = () => import('./components/scatterChart.vue')
const BDMap = () => import('./components/BDmap.vue')
const ChooseCity = () => import('./components/chooseCity.vue')

Vue.use(Router)
export default new Router({
  mode: 'history',
  base: process.env.BASE_URL,
  routes: [
    {
      path: '/',
      name: 'login',
      component: Login
    },
    {
      path: '/home',
      name: 'home',
      component: Home,
      children: [
        {
          path: '/home/',
          name: 'transfer',
          component: Transfer,
          meta: {
            requiresAuth: true
          }
        },
        {
          path: 'album',
          name: 'album',
          component: Album,
          meta: {
            requiresAuth: true
          }
        },
        {
          path: 'barchart',
          name: 'barchart',
          component: BarChart,
          meta: {
            requiresAuth: true
          }
        },
        {
          path: 'linechart',
          name: 'linechar',
          component: LineChar,
          meta: {
            requiresAuth: true
          }
        },
        {
          path: 'piechart',
          name: 'piechart',
          component: PieChart,
          meta: {
            requiresAuth: true
          }
        },
        {
          path: 'liquidfillchart',
          name: 'liquidfillchart',
          component: LiquidfillChart,
          meta: {
            requiresAuth: true
          }
        },
        {
          path: 'worldchart',
          name: 'worldchart',
          component: WorldChart,
          meta: {
            requiresAuth: true
          }
        },
        {
          path: 'chinachart',
          name: 'chinachart',
          component: ChinaChart,
          meta: {
            requiresAuth: true
          }
        },
        {
          path: 'provincechart',
          name: 'provincechart',
          component: ProvinceChart,
          meta: {
            requiresAuth: true
          }
        },
        {
          path: 'scatterchart',
          name: 'scatterchart',
          component: ScatterChart,
          meta: {
            requiresAuth: true
          }
        },
        {
          path: 'BDmap',
          name: 'BDmap',
          component: BDMap,
          meta: {
            requiresAuth: true
          }
        },
        {
          path: 'choosecity',
          name: 'choosecity',
          component: ChooseCity,
          meta: {
            requiresAuth: true
          }
        }
      ]
    },
    {
      path: '/error',
      name: 'error',
      component: Error
    }
  ]
})

登录组件(login.vue)

<template>
  <div class="wrapper">
    <el-form :model="ruleForm" ref="ruleForm" :rules="rules" label-width="80px">
      <el-form-item prop="username" label="用户名">
        <el-input v-model="ruleForm.username"></el-input>
      </el-form-item>
      <el-form-item prop="password" label="密码">
        <el-input v-model="ruleForm.password"></el-input>
      </el-form-item>
      <el-form-item>
        <el-button type="primary" @click="submitForm('ruleForm')">登录</el-button>
        <el-button @click="resetForm('ruleForm')">重置</el-button>
      </el-form-item>
    </el-form>
  </div>
</template>

<script>
import axios from 'axios'
export default {
  data () {
    return {
      ruleForm: {
        username: '',
        password: ''
      },
      rules: {
        username: [
          { required: true, message: '请输入用户名', trigger: 'blur' }
        ],
        password: [
          { required: true, message: '请输入密码', trigger: ['blur', 'change'] }
        ]
      }
    }
  },
  methods: {
    submitForm (formName) {
      let that = this
      this.$refs[formName].validate((valid) => {
        if (valid) {
          axios.get('/api/users.json').then(function (res) {
            let result = res.data.data
            let flag = false
            for (var i = 0; i < result.length; i++) {
              if (result[i].username === that.ruleForm.username && result[i].password 
              === that.ruleForm.password) {
                flag = true
                // 将符合条件的用户ID和昵称存储到本地
                sessionStorage.userId = result[i].id
                sessionStorage.nickname = result[i].nickname
              }
            }
            if (flag) {
              that.$router.push('/home/')
            } else {
              that.$message.error('用户名或密码错误')
            }
          })
        }
      })
    },
    resetForm (formName) {
      this.$refs[formName].resetFields() // 重置用户名和密码
    }
  }
}
</script>

<style scoped>
.wrapper {
  width: 340px;
  border: 1px solid #cccccc;
  border-radius: 5px;
  padding: 50px;
  position: absolute;
  top: 200px;
  left: 50%;
  transform: translateX(-50%);
}
</style>

用来做重定向的组件(transfer.vue)

    登录成功后会进入这个组件,它主要用来跳转到权限列表的第一个路由地址

<template>
  <div class="wrapper"></div>
</template>

<script>
import { mapState } from 'vuex'
export default {
  name: 'transfer',
  data () {
    return {}
  },
  computed: {
    // 使用 mapState 辅助函数会将store中的state映射到局部计算属性中
    ...mapState(['power_list'])
  },
  mounted () {
    // 该页面只做一个重定向的操作
    let redirectUrl = this.power_list[0].path
    this.$router.push({ path: redirectUrl })
  }
}
</script>

<style scoped>
</style>

主体组件(home.vue)

<template>
  <el-container>
    <el-aside>
      <div class="title"><i class="el-icon-apple"></i>后台管理系统</div>
      <el-menu :default-active="selectItem" class="el-menu-vertical-demo" :router="true">
        <el-menu-item :index="item.path" v-for="item in power_list" :key="item.name">
          <i class="el-icon-menu"></i>
          <span slot="title">{{item.name}}</span>
        </el-menu-item>
      </el-menu>
    </el-aside>
    <el-container>
      <el-header style="text-align: right; font-size: 12px;">
        <div class="fold"><i class="el-icon-s-fold"></i></div>
        <el-dropdown @command="handleCommand">
          <el-dropdown-menu slot="dropdown">
            <el-dropdown-item command="personal"><i class="el-icon-user"></i>个人中心</el-dropdown-item>
            <el-dropdown-item command="account"><i class="el-icon-setting"></i>账号设置</el-dropdown-item>
            <el-dropdown-item command="logout"><i class="el-icon-switch-button"></i>退出登录</el-dropdown-item>
          </el-dropdown-menu>
          <el-avatar src="../static/avatar.jpg" shape="square" size="large"></el-avatar>
        </el-dropdown>
        <div class="welcome">欢迎您,{{nickname}}</div>
      </el-header>
      <el-main>
        <router-view/>
      </el-main>
    </el-container>
  </el-container>
</template>

<script>
import { mapState, mapMutations } from 'vuex'
export default {
  data () {
    return {
      nickname: sessionStorage.nickname || '未知用户'
    }
  },
  computed: {
    // 使用 mapState 辅助函数会将store中的state映射到局部计算属性中
    // ...mapState({
    //   power_list: state => state.power_list
    //   selectItem: state => state.selectItem
    // })
    ...mapState(['power_list', 'selectItem'])
  },
  methods: {
    // 使用 mapMutations 辅助函数会将store中的mutations的方法映射到methods中
    // ...mapMutations({
    //   SETPOWERLIST: 'SETPOWERLIST'
    // })
    ...mapMutations(['SETPOWERLIST']),
    handleCommand (command) {
      if (command === 'logout') {
        this.$confirm('此操作将退出系统, 是否继续?', '提示', {
          confirmButtonText: '确定',
          cancelButtonText: '取消',
          type: 'warning'
        }).then(() => {
          sessionStorage.clear() // 清空本地存储
          this.SETPOWERLIST([]) // 将权限列表清空
          this.$router.replace({ path: '/' }) // 返回登录页
        }).catch(() => {
          console.log('取消')
        })
      } else {
        this.$notify.info({
          title: '提示',
          message: '暂时没有该功能哦',
          duration: 3000
        })
      }
    }
  }
}
</script>
<style lang="stylus" scoped>
.el-container
  height 100%
  position relative
.el-header
  height 100%
  font-size 18px !important
  line-height 60px
  background #fff
  border-bottom 1px solid #e6e6e6
  .fold
    float left
    width 40px
    font-size 28px
    text-align left
    cursor pointer
  .welcome
    float right
    margin-right 20px
  .el-dropdown
    float right
    height 100%
    display flex
    align-items center
    font-size 20px !important
    cursor pointer
.el-main
  padding 0
  position relative
.el-aside
  width 210px !important
  border-right solid 1px #e6e6e6 !important
  background #fff
  .title
    height 60px
    line-height 60px
    font-size 22px
    font-weight bold
    padding-left 20px
    box-sizing border-box
    border-bottom 1px solid #e6e6e6
    i
      font-size 25px
  .el-menu
    border-right none
</style>

错误组件(error.vue)

<template>
  <div class="wrapper">
    <h2>您暂无权限,请联系管理员</h2>
    <input type="button" value="返回" class="back" @click="back">
  </div>
</template>

<script>
import { mapState } from 'vuex'
export default {
  data () {
    return {
    }
  },
  computed: {
    // 使用 mapState 辅助函数会将store中的state映射到局部计算属性中
    // ...mapState({
    //   power_list: state => state.power_list
    // })
    ...mapState(['power_list'])
  },
  methods: {
    back () {
      // 如果权限列表不为空,说明是登录后进入的错误页面
      if (this.power_list.length !== 0) {
        this.$router.go(-2) // 返回上一页
      } else {
        // 如果权限列表为空,说明未登录,从地址栏强行进入跳转到的错误页面
        // 或者登陆了却没有权限,跳转到的错误页面
        sessionStorage.clear()
        this.$router.replace({ path: '/' }) // 返回登录页
      }
    }
  }
}
</script>

<style scoped>
h2 {
  margin: 50px 0 0 50px;
  font-size: 40px;
}
.back {
  width: 80px;
  height: 30px;
  line-height: 30px;
  text-align: center;
  background: DarkOrange;
  border-radius: 5px;
  color: #fff;
  margin: 100px;
  cursor: pointer;
}
.back:hover {
  background: orange;
}
</style>

proxy代理处理跨域

    在 vue.config.js 文件里配置(这里是代理到本地)

module.exports = {
  baseUrl: process.env.NODE_ENV === 'production' ? '/' : '/',
  // 输出文件目录
  outputDir: 'dist',
  // 静态资源目录 (js, css, img, fonts)
  assetsDir: 'assets',
  // lintOnSave:{ type:Boolean default:true } 是否使用eslint
  lintOnSave: true,
  // productionSourceMap:{ type:Bollean,default:true } 生产源映射
  // 如果不需要生产时的源映射,那么将此设置为 false 可以加速生产构建
  productionSourceMap: true,
  // devServer: { type: Object } 3个属性 host, port, https, 它支持 webpack-dev-server 的所有选项
  devServer: {
    port: 8080,
    host: 'localhost',
    https: false,
    open: false, // 配置自动启动浏览器
    // 配置跨域处理
    proxy: {
      '/api': {
        target: 'http://localhost:8080',
        pathRewrite: {
          '^/api': '/mock'
        }
      }
    }
  }
}

模拟用的mock数据

     用户列表数据 users.json

{
  "status": true,
  "data": [
    {
      "id": 1,
      "username": "admin",
      "password": "123456",
      "nickname": "凡夫"
    },
    {
      "id": 2,
      "username": "tom",
      "password": "123456",
      "nickname": "Tom"
    },
    {
      "id": 3,
      "username": "jack",
      "password": "123456",
      "nickname": "Jack"
    },
    {
      "id": 4,
      "username": "rose",
      "password": "123456",
      "nickname": "Rose"
    }
  ]
}

     每个用户对应的权限列表数据 power.json

{
  "status": true,
  "data": [
    {
      "userId": 1,
      "powerlist": [
        { "name": "仿去哪相册", "path": "/home/album" },
        { "name": "柱状图", "path": "/home/barchart" },
        { "name": "折线图", "path": "/home/linechart" },
        { "name": "饼状图", "path": "/home/piechart" },
        { "name": "水球图", "path": "/home/liquidfillchart" },
        { "name": "世界地图", "path": "/home/worldchart" },
        { "name": "中国地图", "path": "/home/chinachart" },
        { "name": "省份地图", "path": "/home/provincechart" },
        { "name": "地图散点图", "path": "/home/scatterchart" },
        { "name": "百度地图", "path": "/home/BDmap" },
        { "name": "选择城市", "path": "/home/choosecity" }
      ]
    },
    {
      "userId": 2,
      "powerlist": [
        { "name": "柱状图", "path": "/home/barchart" },
        { "name": "折线图", "path": "/home/linechart" },
        { "name": "饼状图", "path": "/home/piechart" },
        { "name": "水球图", "path": "/home/liquidfillchart" },
        { "name": "百度地图", "path": "/home/BDmap" },
        { "name": "选择城市", "path": "/home/choosecity" }
      ]
    },
    {
      "userId": 3,
      "powerlist": [
        { "name": "折线图", "path": "/home/linechart" },
        { "name": "饼状图", "path": "/home/piechart" },
        { "name": "水球图", "path": "/home/liquidfillchart" },
        { "name": "省份地图", "path": "/home/provincechart" },
        { "name": "地图散点图", "path": "/home/scatterchart" }
      ]
    },
    {
      "userId": 4,
      "powerlist": []
    }
  ]
}

你可能感兴趣的:(Vue)