Skip to content

自定义指令

用于封装 DOM 操作逻辑,提高代码复用性

v-permission:按钮权限控制

用于根据用户权限动态显示/隐藏元素,常用于按钮级别权限控制。

js
// permission.js
import store from '@/store';

export const permission = {
  mounted(el, binding) {
    const { value } = binding;
    const userPermissions = store.getters.permissions; // 用户权限列表
    
    if (value && Array.isArray(value) && value.length > 0) {
      const hasPermission = userPermissions.some(perm => value.includes(perm));
      if (!hasPermission) {
        el.parentNode && el.parentNode.removeChild(el);
      }
    } else {
      console.error('需要指定权限标识,如:v-permission="[\'user:add\']"');
    }
  }
};
// permission.js
import store from '@/store';

export const permission = {
  mounted(el, binding) {
    const { value } = binding;
    const userPermissions = store.getters.permissions; // 用户权限列表
    
    if (value && Array.isArray(value) && value.length > 0) {
      const hasPermission = userPermissions.some(perm => value.includes(perm));
      if (!hasPermission) {
        el.parentNode && el.parentNode.removeChild(el);
      }
    } else {
      console.error('需要指定权限标识,如:v-permission="[\'user:add\']"');
    }
  }
};
js
<button v-permission="['user:delete']">删除用户</button>
<button v-permission="['user:delete']">删除用户</button>

v-role:角色权限控制

根据用户角色决定元素是否显示。

js
// role.js
export const role = {
  mounted(el, binding) {
    const { value } = binding;
    const userRole = localStorage.getItem('user_role'); // 获取用户角色
    
    if (value && value !== userRole) {
      el.style.display = 'none';
    }
  }
};
// role.js
export const role = {
  mounted(el, binding) {
    const { value } = binding;
    const userRole = localStorage.getItem('user_role'); // 获取用户角色
    
    if (value && value !== userRole) {
      el.style.display = 'none';
    }
  }
};
js
<div v-role="'admin'">管理员专属内容</div>
<div v-role="'admin'">管理员专属内容</div>

v-debounce:防抖处理

用于处理高频触发的事件(如按钮点击、输入框搜索),减少性能损耗。

js
// debounce.js
export const debounce = {
  mounted(el, binding) {
    const { value } = binding;
    let timer;
    
    el.addEventListener('click', () => {
      clearTimeout(timer);
      timer = setTimeout(() => {
        value && typeof value === 'function' && value();
      }, 500);
    });
  }
};
// debounce.js
export const debounce = {
  mounted(el, binding) {
    const { value } = binding;
    let timer;
    
    el.addEventListener('click', () => {
      clearTimeout(timer);
      timer = setTimeout(() => {
        value && typeof value === 'function' && value();
      }, 500);
    });
  }
};
js
<button v-debounce="handleClick">搜索</button>
<button v-debounce="handleClick">搜索</button>

v-throttle:节流处理

限制事件触发频率,适用于滚动加载、窗口 resize 等场景。

js
// throttle.js
export const throttle = {
  mounted(el, binding) {
    const { value } = binding;
    let timer;
    
    el.addEventListener('scroll', () => {
      if (!timer) {
        timer = setTimeout(() => {
          value && typeof value === 'function' && value();
          timer = null;
        }, 300);
      }
    });
  }
};
// throttle.js
export const throttle = {
  mounted(el, binding) {
    const { value } = binding;
    let timer;
    
    el.addEventListener('scroll', () => {
      if (!timer) {
        timer = setTimeout(() => {
          value && typeof value === 'function' && value();
          timer = null;
        }, 300);
      }
    });
  }
};
js
<div v-throttle="loadMore" class="scroll-container"></div>
<div v-throttle="loadMore" class="scroll-container"></div>

v-copy:复制文本

js
// directives/copy.js
import { ref, nextTick } from 'vue';

export const copy = {
  // 指令挂载时
  mounted(el, binding) {
    // 创建提示元素
    const createTip = (text) => {
      const tip = document.createElement('div');
      tip.className = 'copy-tip';
      tip.textContent = text;
      tip.style.cssText = `
        position: fixed;
        padding: 8px 16px;
        background: rgba(0,0,0,0.7);
        color: #fff;
        border-radius: 4px;
        font-size: 14px;
        z-index: 9999;
        opacity: 0;
        transition: opacity 0.3s;
      `;
      document.body.appendChild(tip);
      
      // 显示提示
      nextTick(() => {
        tip.style.opacity = '1';
        tip.style.transform = 'translate(-50%, 0)';
      });
      
      // 3秒后隐藏并移除
      setTimeout(() => {
        tip.style.opacity = '0';
        setTimeout(() => {
          document.body.removeChild(tip);
        }, 300);
      }, 3000);
      
      return tip;
    };

    // 复制逻辑
    const copyHandler = () => {
      const text = binding.value;
      if (!text) {
        createTip('复制内容不能为空');
        return;
      }

      // 现代浏览器使用 Clipboard API
      if (navigator.clipboard) {
        navigator.clipboard.writeText(text)
          .then(() => createTip('复制成功'))
          .catch(err => {
            console.error('复制失败:', err);
            createTip('复制失败,请手动操作');
          });
        return;
      }

      // 兼容旧浏览器
      const textarea = document.createElement('textarea');
      textarea.value = text;
      textarea.style.position = 'fixed';
      textarea.style.opacity = '0';
      document.body.appendChild(textarea);
      textarea.select();

      try {
        const result = document.execCommand('copy');
        createTip(result ? '复制成功' : '复制失败');
      } catch (err) {
        console.error('复制失败:', err);
        createTip('复制失败,请手动操作');
      } finally {
        document.body.removeChild(textarea);
      }
    };

    // 绑定点击事件
    el.addEventListener('click', copyHandler);
    
    // 存储事件处理函数,便于后续移除
    el.__copyHandler = copyHandler;
  },

  // 指令更新时
  updated(el, binding) {
    // 如果值发生变化,更新复制内容
    if (binding.value !== binding.oldValue) {
      el.__copyText = binding.value;
    }
  },

  // 指令卸载时
  unmounted(el) {
    // 移除事件监听,避免内存泄漏
    el.removeEventListener('click', el.__copyHandler);
    delete el.__copyHandler;
  }
};
// directives/copy.js
import { ref, nextTick } from 'vue';

export const copy = {
  // 指令挂载时
  mounted(el, binding) {
    // 创建提示元素
    const createTip = (text) => {
      const tip = document.createElement('div');
      tip.className = 'copy-tip';
      tip.textContent = text;
      tip.style.cssText = `
        position: fixed;
        padding: 8px 16px;
        background: rgba(0,0,0,0.7);
        color: #fff;
        border-radius: 4px;
        font-size: 14px;
        z-index: 9999;
        opacity: 0;
        transition: opacity 0.3s;
      `;
      document.body.appendChild(tip);
      
      // 显示提示
      nextTick(() => {
        tip.style.opacity = '1';
        tip.style.transform = 'translate(-50%, 0)';
      });
      
      // 3秒后隐藏并移除
      setTimeout(() => {
        tip.style.opacity = '0';
        setTimeout(() => {
          document.body.removeChild(tip);
        }, 300);
      }, 3000);
      
      return tip;
    };

    // 复制逻辑
    const copyHandler = () => {
      const text = binding.value;
      if (!text) {
        createTip('复制内容不能为空');
        return;
      }

      // 现代浏览器使用 Clipboard API
      if (navigator.clipboard) {
        navigator.clipboard.writeText(text)
          .then(() => createTip('复制成功'))
          .catch(err => {
            console.error('复制失败:', err);
            createTip('复制失败,请手动操作');
          });
        return;
      }

      // 兼容旧浏览器
      const textarea = document.createElement('textarea');
      textarea.value = text;
      textarea.style.position = 'fixed';
      textarea.style.opacity = '0';
      document.body.appendChild(textarea);
      textarea.select();

      try {
        const result = document.execCommand('copy');
        createTip(result ? '复制成功' : '复制失败');
      } catch (err) {
        console.error('复制失败:', err);
        createTip('复制失败,请手动操作');
      } finally {
        document.body.removeChild(textarea);
      }
    };

    // 绑定点击事件
    el.addEventListener('click', copyHandler);
    
    // 存储事件处理函数,便于后续移除
    el.__copyHandler = copyHandler;
  },

  // 指令更新时
  updated(el, binding) {
    // 如果值发生变化,更新复制内容
    if (binding.value !== binding.oldValue) {
      el.__copyText = binding.value;
    }
  },

  // 指令卸载时
  unmounted(el) {
    // 移除事件监听,避免内存泄漏
    el.removeEventListener('click', el.__copyHandler);
    delete el.__copyHandler;
  }
};

全局注册与使用

js
// directives/index.js
import { permission } from './permission';
import { debounce } from './debounce';

export default {
  install(app) {
    app.directive('permission', permission);
    app.directive('debounce', debounce);
    // 其他指令...
  }
};
// directives/index.js
import { permission } from './permission';
import { debounce } from './debounce';

export default {
  install(app) {
    app.directive('permission', permission);
    app.directive('debounce', debounce);
    // 其他指令...
  }
};

main.js中引入并注册全局指令

js
import { createApp } from 'vue';
import App from './App.vue';
import directives from './directives';

const app = createApp(App);
app.use(directives);
app.mount('#app');
import { createApp } from 'vue';
import App from './App.vue';
import directives from './directives';

const app = createApp(App);
app.use(directives);
app.mount('#app');

程序员小洛文档