“如何控制按钮权限”,真实案例带你一步一步实现!让面试不再踩坑。

    |     2024年5月14日   |   vuejs, web前端技术   |     0 条评论   |    190

后台管理系统里,菜单权限管的是「进哪个模块」;要更细地管增、删、改、查、搜索,得用按钮权限:按角色决定按钮显不显示、能不能点。

面试常踩的坑是:只藏按钮、接口仍能调;或者三种写法(函数、组件、指令)讲不清。下面先看汇智在线的真实效果,再拆实现,最后落到项目里的插件封装。

一、真实场景:汇智在线

给「商城用户」角色不设置商品删除、banner 删除、会员删除权限,该角色登录后这些按钮就不会出现。

商城用户角色下,对应删除权限是关掉的:

超级管理员角色打开全部按钮操作权限。周 sir 以超管登录后,商品管理里能看到删除商品按钮:

同一界面,商城用户登录则没有删除按钮:

要点:权限码来自后端角色配置,前端只根据当前用户的资源列表决定渲染。这是展示层控制,接口仍须鉴权。

二、函数方式

用统一方法 hasPermission,在模板里 v-if:

<template>
  <a-button v-if="hasPermission(['20000', '2000010'])" color="error" class="mx-4"> 拥有[20000,2000010]code可见 </a-button>
</template>
<script lang="ts">
import { usePermission } from '/@/hooks/web/usePermission'
export default defineComponent({
  setup() {
    const { hasPermission } = usePermission()
    return { hasPermission }
  }
})
</script>

hasPermission 从全局取出当前用户权限码,判断按钮需要的码在不在里面;传数组时,命中其中一个即可:

export function usePermission() {
  function hasPermission(value, def = true) {
    // 默认视为有权限
    if (!value) {
      return def
    }
    const allCodeList = permissionStore.getPermCodeList
    if (!isArray(value)) {
      return allCodeList.includes(value)
    }
    // intersection是lodash提供的一个方法,用于返回一个所有给定数组都存在的元素组成的数组
    return intersection(value, allCodeList).length > 0
    return true
  }
}

最后一行 return true 永远走不到,属于死代码,面试时主动提一句更好。多码用 intersection 做「有交集即通过」。

三、组件方式

也可以包一层 Authority,把权限码通过 value 传入:

<template>
  <div>
    <Authority :value="RoleEnum.ADMIN"> <a-button type="primary" block> 只有admin角色可见 </a-button> </Authority>
  </div>
</template>
<script>
import { Authority } from '/@/components/Authority'
import { defineComponent } from 'vue'
export default defineComponent({ components: { Authority } })
</script>

有权限就原样渲染插槽,没有就什么都不渲染:

<script lang="ts">
import { defineComponent } from 'vue'
import { usePermission } from '/@/hooks/web/usePermission'
import { getSlot } from '/@/utils/helper/tsxHelper'
export default defineComponent({
  name: 'Authority',
  props: { value: { type: [Number, Array, String], default: '' } },
  setup(props, { slots }) {
    const { hasPermission } = usePermission()
    function renderAuth() {
      const { value } = props
      if (!value) {
        return getSlot(slots)
      }
      return hasPermission(value) ? getSlot(slots) : null
    }
    return () => {
      return renderAuth()
    }
  }
})
</script>

四、指令方式

模板更短,写成 v-auth:

<a-button v-auth="'1000'" type="primary" class="mx-4"> 拥有code ['1000']权限可见 </a-button>
import { usePermission } from '/@/hooks/web/usePermission'
function isAuth(el, binding) {
  const { hasPermission } = usePermission()
  const value = binding.value
  if (!value) return
  if (!hasPermission(value)) {
    el.parentNode?.removeChild(el)
  }
}
const mounted = (el, binding) => {
  isAuth(el, binding)
}
const authDirective = {
  // 在绑定元素的父组件
  // 及他自己的所有子节点都挂载完成后调用
  mounted,
}
// 注册全局指令
export function setupPermissionDirective(app) {
  app.directive('auth', authDirective)
}

只在 mounted 里判断:没有权限就从 DOM 拿掉节点。权限后来变了不会自动更新,这是指令方案的常见坑。

五、汇智在线:插件封装

实际项目用插件封装指令,耦合更干净:根据登录后存进 store 的按钮资源,对照指令上的权限值,决定显示或移除。

import { useMenuStore } from '@/stores/modules/menu'

export const myPlugin = {
    // 插件封装全局组件、全局指令、全局...
  install(app: any, options: any) {
    // 定义v-permession指令, 在 `mounted` 和 `updated` 时都调用
    app.directive('permession', (el: any, binding: any) => {
      // 1. 获取登录时存储store中的按钮资源数据
      const menuStore = useMenuStore()

      // 2. 获取当前角色资源列表 [AddProduct,EditProduct...]
      const resourceList = menuStore.resourceList

      // 3. 按钮绑定的权限值 binding.value
      const value = binding.value

      if (value) {
        const isHide = resourceList && resourceList.some(item => value.includes(item))
        if (!isHide) {
          el && el.remove()
        } else {
          console.log('need rols! Like v-perisson ="AddUser"')
        }
      }
    })
  }
}

指令名按项目写成了 v-permession(拼写如此)。商品删除按钮这样绑:

<el-button type="primary" size="small" v-permession="'DeleteProduct'">删除</el-button>

登录成功后并行拉菜单和按钮资源,写入 store:

// 调用菜单资源接口
  let menuPromise = RequestMenu(roleId)
  // 调用按钮资源接口
  let resourcePromise = RequestReourceAuthor(roleId)
  //多个promise封装的异常操作用上个方法实现 Promise.all([promise,promise]) Promise.race([promise,promise])
  const dataList: any = await Promise.all([
      menuPromise,
      resourcePromise,
  ])
  const menuData = dataList[0]
  const resourceData = dataList[1]
  if (menuData.resultCode === 1) {
      // 1.保存菜单接口数据
      this.menuList = menuData.resultInfo
      // 2.格式化菜单数据
      let routeMenuList = formateRouterMenu(menuData.resultInfo)
      // 3.添加动态路由,router对象
      routeMenuList.forEach((route: any) =>
          router.addRoute(route)
      )
  }
  if (resourceData.resultCode === 1) {
      // 保存按钮资源数据
      this.resourceList = resourceData.resultInfo.resource
  }
写法 怎么用 优点 面试坑
函数 v-if="hasPermission(...)" 逻辑集中、好测 模板里容易漏写 v-if
组件 插槽语义清楚 包一层,简单按钮略重
指令 / 插件 v-auth / v-permession 模板最短 只 mounted 时不响应权限变化;前端隐藏 ≠ 接口安全

长按上图,识别图中二维码即可关注。

推荐阅读:

动态权限菜单是什么?

封装递归组件轻松实现ElementPlus 表单组件二次封装,提高50%开发效率

ElementPlus 表格组件二次封装,提高50%开发效率

token是什么? 无感刷新token技术让项目安全性与体验性完美平衡

一句话总结:按钮权限是用角色资源码决定渲染;函数、组件、指令三种壳,核心都是比对权限码,并且后端接口必须再鉴一次。

转载请注明来源:“如何控制按钮权限”,真实案例带你一步一步实现!让面试不再踩坑。
本文链接地址:https://ai.zhousir.top/?p=3504
回复 取消