模板语法 - 拓展
自定义指令
在 Vue 中,你可以使用Vue.directive()
方法来创建自定义指令。以下是一个基本的步骤说明如何创建自定义指令:
Vue2.x定义自定义指令
全局定义:
Vue.directive('my-directive', {
bind(el, binding, vnode) {
// 当指令绑定到元素上时触发
// el 是指令绑定的元素
// binding 是一个对象,包含以下属性:
// - name: 指令名,不包括 v- 前缀。
// - value: 指令的绑定值,例如:v-my-directive="1 + 1" 中,绑定值为 2。
// - oldValue: 指令绑定的前一个值,仅在 update 和 componentUpdated 钩子中可用。无论值是否改变都可用。
// - expression: 字符串形式的指令表达式。例如 v-my-directive="1 + 1" 中,表达式为 "1 + 1"。
// - arg: 传给指令的参数,可选。例如 v-my-directive:foo 中,参数为 "foo"。
// - modifiers: 一个包含修饰符的对象。例如:v-my-directive.foo.bar 中,修饰符对象为 { foo: true, bar: true }。
// vnode 是 Vue 编译生成的虚拟节点
},
inserted(el, binding, vnode) {
// 当被绑定的元素插入到 DOM 中时触发
},
update(el, binding, vnode, oldVnode) {
// 当指令的绑定值更新时触发
// oldVnode 是上一个虚拟节点
},
componentUpdated(el, binding, vnode, oldVnode) {
// 当组件的 VNode 及其子 VNode 更新时触发
},
unbind(el, binding, vnode) {
// 当指令与元素解绑时触发
}
})
局部定义(在组件选项中):
directives: {
'my-directive': {
// ... 与全局定义相同 ...
}
}
Vue3.x定义自定义指令
app.directive('my-directive', {
beforeMount(el, binding, vnode, prevVnode) {
// 当指令绑定到元素上之前触发
// el 是指令绑定的元素
// binding 是一个对象,包含与 Vue 2.x 类似的属性
// vnode 是 Vue 编译生成的虚拟节点
// prevVnode 是前一个虚拟节点,如果是初始渲染则为 null
},
mounted(el, binding, vnode, prevVnode) {
// 当被绑定的元素插入到 DOM 中后触发
},
beforeUpdate(el, binding, vnode, prevVnode) {
// 当指令的绑定值可能发生变化时触发
// 此时元素和组件尚未更新
},
updated(el, binding, vnode, prevVnode) {
// 当指令的绑定值更新,并且元素和组件已经更新后触发
},
beforeUnmount(el, binding, vnode, prevVnode) {
// 当指令与元素解绑之前触发
},
unmounted(el, binding, vnode, prevVnode) {
// 当指令与元素解绑后触发
}
});
在 Vue 3.x 中,自定义指令的生命周期钩子函数与 Vue 2.x 有所不同,主要有以下几点变化:
bind
和inserted
在 Vue 3.x 中合并成了mounted
。update
在 Vue 3.x 中变成了beforeUpdate
和updated
。componentUpdated
在 Vue 3.x 中不再存在,因为updated
钩子现在是在 DOM 更新之后调用的。unbind
在 Vue 3.x 中变成了unmounted
。
实战模拟
按钮级权限控制
import {createApp} from 'vue'
import App from './App.vue'
import store from './store'
import router from './router';
// 创建vue实例(声明为Vue而不是app等其他的原因是如果不命名为Vue,我的WebStorm的版本在组件中使用自定义指令会报警告,可能是因为这个版本还没有完全兼容好Vue3.x的写法,使用Vue命名是为了让它识别成Vue2.x的写法就不报警告了。强迫症患者)
const Vue = createApp(App);
// 检查用户权限
function checkUserPermission(userPermissions, requiredPermission) {
return userPermissions.includes(requiredPermission);
}
// 封装显示/隐藏元素的逻辑
function toggleElementDisplay(el, show) {
el.style.display = show ? '' : 'none';
}
// 在Vue应用实例上定义全局自定义指令
Vue.directive('permission', {
mounted(el, binding) {
const hasPermission = checkUserPermission(store.state.user.userPermissions, binding.value);
toggleElementDisplay(el, hasPermission);
},
updated(el, binding) {
const hasPermission = checkUserPermission(store.state.userPermissions, binding.value);
toggleElementDisplay(el, hasPermission);
}
});
Vue.use(store).use(router);
// 挂载实例
Vue.mount('#app');
这样一个全局的权限控制指令就完成了。
既然说到了权限控制,那就不得不说一下页面级别的权限控制。
页面路由 - 页面级权限控制
import {createRouter, createWebHistory} from 'vue-router';
import {useStore} from "vuex";
const routes = [
{
path: '/',
name: 'Index',
meta: {
title: '首页',
keepAlive: true,
permissionName: 'default',
requireAuth: true // 标记这个路由需要权限
},
component: () => import('@/pages/Home.vue')
},
{
path: '/ces',
name: 'ces',
meta: {
title: 'ces',
keepAlive: true,
permissionName: 'admin',
requireAuth: true // 标记这个路由需要权限
},
component: () => import('@/pages/ces.vue')
}
];
const router = createRouter({
history: createWebHistory(),
routes
});
// 检查用户是否有权限访问某个路由
function hasPermission(to) {
const store = useStore();
const user = store.state.user;
// 检查用户是否已登录,以及是否有访问该路由的权限
// 这通常涉及到检查用户的角色或其他权限信息
return store.state.isLoggedIn && (to.meta.requireAuth ? user.hasPermission(to) : true);
}
// 全局前置守卫
router.beforeEach((to, from, next) => {
const store = useStore(); // 获取 Vuex store 实例
const user = store.state.user; // 获取用户状态
const isLoggedIn = store.getters.isLoggedIn; // 获取登录状态
const hasPermission = store.getters.hasPermission; // 获取权限检查函数
if (to.meta.requireAuth) {
if (isLoggedIn && hasPermission(to.meta.permissionName)) {
// 用户已登录且拥有权限,进入路由
next();
} else {
alert('权限不符,请确认权限')
// 用户未登录或没有权限,重定向到登录页面或其他处理逻辑
next('/');
}
} else {
// 不需要权限的路由,直接通过
next();
}
});
export default router;
使用 Vue Router 的全局前置守卫 beforeEach
,在每个路由跳转前执行权限检查。如果用户没有权限访问目标路由,就使用 next
函数重定向到登录页面或其他处理逻辑。