普通视图

发现新文章,点击刷新页面。
昨天 — 2025年11月6日首页

Vue 权限控制神技!自定义 auth 指令优雅实现按钮级权限管理

作者 apollo_qwe
2025年11月6日 15:01

Vue 权限控制神技!自定义 auth 指令优雅实现按钮级权限管理

在中后台系统开发中,按钮级别的权限控制是常见需求 —— 不同角色用户看到的操作按钮可能不同,直接写 if-else 判断又会导致代码冗余混乱。今天分享一个 Vue 自定义指令v-auth,一行代码就能搞定按钮的显示、隐藏或禁用,大幅提升代码整洁度!

一、指令核心功能

这个v-auth指令基于 Vuex 存储的用户权限数据,实现两大核心能力:

  1. 超级管理员自动放行,无需额外判断

  2. 普通用户支持两种权限控制模式:

    • 隐藏模式:无权限时直接移除 DOM 元素
    • 禁用模式:无权限时保留元素但添加禁用状态和样式

二、完整代码实现

// 权限控制指令:v-auth
Vue.directive('auth', {
  async inserted(el, binding) {
    const { value, modifiers } = binding;
    
    // 确保权限数据已加载,未加载则异步获取
    if (!store.getters.permissions) {
      //获取权限数据
    }
    
    const permissions = store.getters.permissions || []; // 兜底处理,避免报错

    // 超级管理员特权:拥有所有权限直接放行
    if (permissions.includes('*:*:*')) return;

    // 权限校验核心逻辑
    const isDisabled = modifiers.disabled; // 是否启用禁用模式
    const hasPermission = permissions.includes(value); // 校验用户是否拥有目标权限
    
    if (!hasPermission) {
      if (isDisabled) {
        // 禁用模式:添加禁用属性和自定义样式
        el.disabled = true;
        el.classList.add('disabled-button');
      } else {
        // 隐藏模式:从DOM中移除元素
        el.parentNode?.removeChild(el);
      }
    }
  }
});

三、代码逻辑逐行解析

1. 指令触发时机

使用inserted钩子,在元素插入 DOM 后执行校验,确保操作目标元素存在。

2. 权限数据加载

  • 先检查 Vuex 中是否已缓存权限数据
  • 未加载则调用user/getInfo异步接口获取,等待加载完成再继续校验

3. 权限判断逻辑

  • 超级管理员通过*:*:*标识直接放行,适配系统最高权限场景
  • 普通用户通过binding.value获取需要校验的权限标识(如"user:add"
  • 通过binding.modifiers.disabled切换控制模式,灵活适配不同 UI 需求

四、实际使用场景

1. 隐藏模式(默认)

无权限时直接隐藏按钮,适用于非核心操作按钮:

<el-button v-auth="'user:add'">新增用户</el-button>

2. 禁用模式

无权限时保留按钮但禁用,适用于需要提示用户权限不足的场景:

<el-button v-auth.disabled="'user:edit'">编辑用户</el-button>

六、总结

按钮权限控制的工作流程图:

flowchart TD
    A(("① 用户登录")) --> B[系统初始化]
    B --> C[渲染带v-auth的组件]
    C --> D{指令解析}
    
    D --> |解析到v-auth节点| E["② inserted钩子触发"]
    E --> F{权限数据已加载?}
    F -- 否 --> G["③ 调用store.dispatch()"]
    G --> H[获取用户权限数据]
    H --> F
    F -- 是 --> I{是超级管理员?}
    I -- 是 --> J["✅ 放行渲染"]
    I -- 否 --> K["④ 权限校验"]
    
    K --> L{检查修饰符}
    L -- disabled --> M["⑤ 禁用模式处理"]
    L -- 无修饰符 --> N["⑤ 隐藏模式处理"]
    
    M --> O{权限匹配?}
    O -- 匹配 --> P["✅ 保持可用状态"]
    O -- 不匹配 --> Q["🛑 添加disabled属性"]
    
    N --> R{权限匹配?}
    R -- 匹配 --> S["✅ 正常显示"]
    R -- 不匹配 --> T["🛑 移除DOM节点"]
    
    Q --> U[结束]
    T --> U
    P --> U
    S --> U
    
    style A fill:#4CAF50,color:white
    style G fill:#2196F3,color:white
    style Q fill:#FF5722,color:white
    style T fill:#FF5722,color:white
    style J fill:#4CAF50,color:white

这个v-auth指令将权限控制逻辑封装复用,摆脱了模板中大量的权限判断代码,让权限管理更优雅、维护成本更低。适用于各类中后台系统的按钮、菜单等元素权限控制,搭配 Vuex 的状态管理可实现全系统权限统一管控。

昨天以前首页

封装axios实现全局loading,在一定程度上减少重复请求的发生

作者 apollo_qwe
2025年11月5日 10:51
解决了 “在每个页面 / 按钮上手动绑定 loading” 的问题,大幅减少了重复代码
核心代码如下:
import axios from 'axios';
import { Loading } from 'element-ui';
// 定义不同API类型的超时时间(单位:毫秒)
const TIMEOUT_CONFIG = {
  default: 3000, // 默认超时时间
  fast: 1000,     // 快速API,简单查询等
  normal: 5000,  // 普通API,大多数业务接口
  slow: 10000,    // 慢速API,文件上传、大数据量处理
  critical: 15000 // 关键API,支付、重要业务处理
}

const service = axios.create({
  baseURL: process.env.VUE_APP_BASE_API, 
  timeout: TIMEOUT_CONFIG.default 
});

/**
 *  loading 计数
 *  loadingCount //请求次数,为0时,结束loading
 **/
 let loadingCount = 0
 let isLoading = false
 let loadingInstance = null
 const noLoadingApi = ['a','b','c'] // 禁止触发全局loading的路由
 const addLoading = (url) => {
 const result = noLoadingApi.includes(url)
  if(result){
    return;
  }
   loadingCount++
   if (!isLoading){
    loadingInstance = Loading.service({
      lock: true,
      background: 'rgba(0, 0, 0, 0.9)'
    })
    isLoading = true
   }
 }
 
 const closeLoading = (url) => {
 const result = url && noLoadingApi.includes(url)
  if(result){
    return;
  }
   loadingCount--
   if (loadingCount <= 0) {
    loadingInstance && loadingInstance.close()
    isLoading = false
   }
 }

service.interceptors.request.use(
  config => {
    // 定义关键API路径数组
    const CRITICAL_API_PATHS = ['a','b','c'];
    
    // 检查是否是关键API
    if (CRITICAL_API_PATHS.some(path => config.url.includes(path))) {
      config.timeout = TIMEOUT_CONFIG.critical
    }
    addLoading(config.url)
    //....根据需求自定义封装请求头
  
    return config;
  },
  error => {
    return Promise.reject(error);
  }
);

service.interceptors.response.use(
  response => {
    closeLoading(response.config.url)
    //...根据需求自定义封装响应头
  },
  error => {
    closeLoading()
    return Promise.reject(error);
  }
);

export default service;

1. 全局统一管理 loading,无需页面 / 按钮单独处理

代码通过 axios 的请求 / 响应拦截器,对所有经过 service 实例的请求进行统一拦截:

  • 请求发起时:自动调用 addLoading 显示全局 loading(除非接口在 noLoadingApi 白名单中);
  • 请求完成时(成功 / 失败):自动调用 closeLoading 关闭全局 loading(当所有并发请求都完成时)。

这种方式下,无论哪个页面、哪个按钮触发的请求,只要使用了这个 service 实例,都不需要在页面中手动写 loading.show() 或 loading.hide(),完全由拦截器自动处理。

2. 避免了 “重复编写 loading 控制逻辑” 的冗余

如果没有这段代码,通常的做法是:

  • 在每个按钮点击事件中,先手动显示 loading;
  • 在请求的 then/catch 中手动隐藏 loading;
  • 还要处理多个请求并发时,loading 被提前关闭的问题(比如两个请求同时发起,第一个完成就关 loading,导致第二个请求无 loading)。

而这段代码通过 loadingCount 计数和拦截器统一控制,一次性解决了 “显示 / 隐藏时机”“并发请求 loading 管理” 等问题,所有页面 / 按钮都能复用这套逻辑,无需重复编写。

3. 特殊场景通过配置排除,灵活性兼顾

代码中通过 noLoadingApi 数组定义了 “不需要 loading 的接口”,对于这些特殊接口,无需在页面中单独处理,只需在全局配置中维护这个数组即可,进一步减少了页面级的重复配置。

总结

这段代码通过 “拦截器 + 全局配置” 的方式,实现了 loading 的 “一次编写,全项目复用”,彻底避免了在每个页面、每个按钮中重复编写 loading 控制逻辑的工作,显著减少了冗余代码,同时还解决了并发请求下的 loading 显示问题,是一种高效的全局状态管理方案。

❌
❌