Appearance
自定义指令
用于封装 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');