RABC permission management (detailed)

RABC authority design idea

In order to see different pages and perform different functions after different accounts (employees and presidents) log in to the system, RABC(Role-Based Access control) permission model is to allocate visual pages according to the permissions of roles.

Three key points:

User: the person who uses the system
Role: what is the position of the person using the system (employee, manager, President)
Permission point: what the position can do (function module in the left menu bar - > add, delete, modify and query)

Test process:
① Add an employee on the employee management page. This is the user of the three elements
② Assign roles to new employees
③ Assign permissions to roles in company settings

💢 Permissions in the system cannot be added at will. They must be developed permissions (pages that can be realized in the menu bar on the left)
💢 There is a one to many relationship between users and roles, and one person has several roles.

Concrete implementation

1. Assign roles
Click Assign role and pop-up box, which contains a list of existing roles. When you click Assign role, pass the id to display the existing roles of the current user according to the id.

Assign role parent component Src / employees / employee vue

<template slot-scope="scope">
	<el-button type="text" size="smell" @click="assignFn(scope.row)">Assign roles</el-button>
 </template>
 <el-dialog
      title="Assign roles"
      :visible.sync="showDialogRole"
      :close-on-press-escape="false"
      :close-on-click-modal="false"
      @close="showDialogRole=false"
    >
      <assign-role :id="curId" ref="assignRole" @close="showDialogRole=false" />
    </el-dialog>
 
 //Import import recovery box subcomponent
 // -------------------------------------------Assign roles----------------------------------
    assignFn(row) {
      this.showDialogRole = true
      this.curId = row.id
      this.$nextTick(() => {
        this.$refs.assignRole.getRoleListFn()
      })
    }

Sub component of assigned role: employees / assignrole vue

<template>
  <div>
    <el-checkbox-group v-model="rolesList">
      <el-checkbox v-for="item in checkList" :key="item.id" :label="item.id">{{ item.name }}</el-checkbox>
    </el-checkbox-group>
    <div style="margin-top: 20px; text-align: right">
      <el-button type="primary" @click="submitFn">determine</el-button>
      <el-button @click="closeDialog">cancel</el-button>
    </div>
  </div>
</template>
<script>
import { getAllRoleAPI } from '@/api/settings'
import { getDetailInfo } from '@/api/user.js'
import { assignRolesAPI } from '@/api/employees.js'
export default {
  name: 'AssignRole',
  props: {
    id: { type: String,	required: true }
  },
  data() {
    return {
      checkList: [], // Role list
      rolesList: []// The user already has a role
    }
  },
  created() {
  },
  methods: {
    // ------------------------------------Submit role-----------------------------------
    async  submitFn() {
      const resp = await assignRolesAPI({ id: this.id, roleIds: this.rolesList })
      console.log(resp)
      this.$emit('close')
    },
    // ----------------------------------Get role list----------------------------------
    async  getRoleListFn() {
      const resp = await getAllRoleAPI({ page: 1, pagesize: 100 })
      console.log(resp)
      this.checkList = resp.data.rows
      const res = await getDetailInfo(this.id)
      console.log(res)
      this.rolesList = res.data.roleIds
    },
    // -------------------------------------Cancel button------------------------------------
    closeDialog() {
      this.$emit('close')
    }
  }
}
</script>

💢 In El checkbox group v-model = "rolesList", the bound value of v-model is an array, indicating that multiple choices can be made.
💢 When rendering data in a template

{{ item.name }}
Where label determines the currently selected value, {{role name to be displayed}}

2. Assign permissions

In parent component (Views / settings / settings. Vue): prepare pop-up - > register event - > provide data method

<template>
  <div class="settings-container">
    <div class="app-container">
      <el-card>
        <!-- Specific page structure -->
        <el-tabs>
          <!-- Placement tab -->
          <el-tab-pane label="Role management">
            <!-- form -->
            <el-table :data="tableList">
              <el-table-column label="operation">
                <!-- scope It's just the name of the slot. What's important is the name in it.row This is the object of each line, which is a fixed writing method -->
                <template slot-scope="scope">
                  <el-button size="small" type="success" @click="hAssign(scope.row.id)">Assign permissions</el-button>
                </template>
              </el-table-column>
            </el-table>
            <el-row type="flex" justify="center" align="middle" style="height: 60px">
            </el-row>
          </el-tab-pane>
        </el-tabs>
      </el-card>
      <!-- Bomb layer for assigning permissions -->
      <el-dialog
        title="Assign permissions(The first level is the view permission of the routing page-The second level is button operation authority)"
        :visible.sync="showDialogAssign"
      >
        <assign-permission ref="assignPermission" :role-id="roleId" @close="showDialogAssign=false" />
      </el-dialog>
    </div>
  </div>
</template>
<script>
export default {
  name: 'Setting',
  components: {
    assignPermission
  },
  data() {
    return {
      showDialogAssign: false, // Assign permissions dialog box
  methods: {
    // -----------------------------------------Assign permissions-------------------------------------
    hAssign(id) {
      this.roleId = id
      this.showDialogAssign = true
      this.$nextTick(() => {
        this.$refs.assignPermission.getRoleDetail()
      })
    }
  }
}
</script>

In the sub component (settings/assignPermission.vue):

<template>
  <div>
    <!-- Permission point data display:check-strictly  set up true,You can turn off parent-child associations -->
    <el-tree
      ref="tree"
      :data="permissionData"
      :props="{ label: 'name' }"
      node-key="id"
      default-expand-all
      :show-checkbox="true"
      :check-strictly="true"
    />
    <div style="text-align:right;">
      <el-button @click="hCancel">cancel</el-button>
      <el-button type="primary" @click="getAssignRoleFn">determine</el-button>
    </div>
  </div>
</template>

<script>
import { getPermissionListAPI } from '@/api/permissions.js'
import { tranListToTreeData } from '@/utils/index.js'
import { getRoleDetail, getAssignRoleAPI } from '@/api/settings.js'
export default {
  props: {
    roleId: {
      type: String,
      required: true
    }
  },
  data() {
    return {
      permissionData: [] // Store permission data
    }
  },
  created() {
    this.getPermissionListFn()
  },
  methods: {
    // -------------------------------------------Get permission list-------------------------------------
    async  getPermissionListFn() {
      const resp = await getPermissionListAPI()
      console.log(resp)
      this.permissionData = tranListToTreeData(resp.data)
      console.log('Array to tree', this.permissionData)
    },
    // --------------------------------------------Get role details------------------------------------
    async  getRoleDetail() {
      const resp = await getRoleDetail(this.roleId)
      console.log(resp)
      // Backfill to tree
      this.$refs.tree.setCheckedKeys(resp.data.permIds)
    },
    // ---------------------------------------------Close the bomb layer------------------------------------------
    hCancel() {
    // Close the spring layer through the parent component
      this.$emit('close')
      // The next time you get the role permission array based on id, empty the container to avoid affecting the next save
      this.$refs.tree.setCheckedKeys([])
    },
    // ------------------------------------------Assign permissions to roles--------------------------------------
    async getAssignRoleFn() {
      const pid = this.$refs.tree.getCheckedKeys()
      const resp = await getAssignRoleAPI({ id: this.roleId, permIds: pid })
      console.log(resp)
      this.hCancel()// Notify parent component to close bomb layer
      this.$message.success('Allocation succeeded')
    }
  }
}
</script>

<style>

</style>

3. Page permission control

1. Menu permission control on the left (different users will see different menus after entering the system)
 2. Operation button permission control (Different people have different permissions for the buttons on the page)
 3. Location of permission data: the following figure shows the permissions that administrators can see when logging in.![Insert picture description here](https://img-blog.csdnimg.cn/5bb6a3a086d6498c911224f19bc2840e.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBA5YuH5pWi54mb54mb77yM5Yay5Yay5Yay,size_11,color_FFFFFF,t_70,g_se,x_16)3.1 modify permission data

Only administrators can modify permission data, so first add users - > assign roles - > assign permissions, and re observe permission data (data.roles.menu,
points)

3.2 dynamically generate the left menu
The new user logs in successfully, and the page jumps and enters the navigation guard

3.3 in router / index In the routing configuration in JS, the part that deletes dynamic routing is changed to: routes: [... constantRoutes]
3.4 in permission JS and add dynamically with addRoutes. At this time, only the static home page is left for the dynamic route on the left. You can enter the address in the address bar to jump (the role of addRoutes)
3.5 returning menu items from actions

  async getUserInfo(context) {
      // 1. ajax obtains basic information, including user id
      const rs = await getUserInfoApi()
      console.log('Used to obtain user information,', rs)
      // 2. Resend the request according to the user id (rs.data.userId) to obtain the details (including the avatar)
      const info = await getUserDetailById(rs.data.userId)
      console.log('Get details', info.data)
      // Merge the two copies obtained above and save them to vuex
      context.commit('setUserInfo', { ...info.data, ...rs.data })
      // The current user can see the menu res.data roles. menus
+     return rs.data.roles.menus
    },

3.6 in permission Get the return value of action in JS and filter it

/ Introduce all dynamic routing tables(Unfiltered)
+ import router, { asyncRoutes } from '@/router'

const whiteList = ['/login', '/404']
router.beforeEach(async(to, from, next) => {
  // Open progress bar
  NProgress.start()
  // Get local token global getter
  const token = store.getters.token
  if (token) {
    // There is a token
    if (to.path === '/login') {
      next('/')
    } else {
      if (!store.getters.userId) {
+        const menus = await store.dispatch('user/getUserInfo')
		 //Filter dynamic arrays by permissions
+        const filterRoutes = asyncRoutes.filter(route => {
+        const routeName = route.children[0].name
+        return menus.includes(routeName)
+       })
        // 1. Rewrite to dynamic addition
+       router.addRoutes(filterRoutes)
        //2. When generating the left menu, you should also get it from vuex
+       store.commit('menu/setMenuList', filterRoutes)
+       //3. Solve the white screen bug during refresh
        next({ ...to, // Ensure that the route is added before entering the page (which can be understood as entering again)
          replace: true// Go back again without retaining the duplicate history
        })
      }else{
      next()
   }
  } else {
    // No token
    if (whiteList.includes(to.path)) {
      next()
    } else {
      next('/login')
    }
  }
  // End progress bar
  NProgress.done()
})
 💢Current menu(src\layout\components\Sidebar\index.vue)Data used: this.$router.options.routes **You can get the current routing configuration and the set routing table data**But this data is fixed, so replace this data with this.$router.options.routes You can get the data of the routing table dynamically.

💢 If you want to display the routing table data in the left menu bar immediately after calling the addRoutes method, save the dynamic routing menu in vuex

3.7 fixing bug s
3.7. 1 solve the problem of refreshing the white screen (/ / 3...)
3.7. 2 after exiting, log in again and find that the menu is abnormal (the console has an output saying that the route is repeated)

Reason: route setting is through router Addroutes (filterroutes) is added. When exiting, it is not cleared. Log in again and add it again, so it is repeated.
You need to reset the routing permission (restore the default) and add it again after logging in in the future. Otherwise, it will be added repeatedly
solve:
router/index.js file, there is a reset routing method

// Reset route
export function resetRouter() {
  const newRouter = createRouter()
  router.matcher = newRouter.matcher // Reset the matching path of the route
}

When logging out, you can call store / modules / user js

import { resetRouter } from '@/router'
// Action action to exit
logout(context) {
  // 1. Remove vuex personal information
  context.commit('removeUserInfo')
  // 2. Remove token information
  context.commit('removeToken')
  // 3. Reset route
  + resetRouter()
}

4. Button level control

4.1 user defined instructions: self defined instructions. Because the instructions are not enough, we need to define them ourselves.

4.2 solve button level permission verification - in main JS, define global instructions

// Register a global custom directive ` v-allow`
Vue.directive('allow', {
  inserted: function(el, binding) {
    // Take points from vuex,
    const points = store.state.user.userInfo.roles.points
    // If points has binding Value is displayed
    if (points.includes(binding.value)) {
      // console.log('judge whether this element will be displayed ', el, binding.value)
    } else {
     // el.style.display = 'none', this is only hidden. Those who understand the business can also be displayed through inspection, so they should be destroyed
      el.parentNode.removeChild(el)
    }
  }
})

use

<el-button
+           v-allow="'import_employee'"
            type="warning"
            size="small"
            @click="$router.push('/import')"
          >Import excel</el-button>

※ here: 'import_ 'employee' comes from an identifier

Keywords: Javascript Vue.js

Added by Nat on Sun, 19 Dec 2021 18:57:24 +0200